fix #1006: password generator patterns

This commit is contained in:
antelle 2020-03-15 11:20:01 +01:00
parent c4be2e5d00
commit 8021c6bb81
9 changed files with 96 additions and 63 deletions

View File

@ -47,23 +47,20 @@ const GeneratorPresets = {
name: 'Mac',
title: Locale.genPresetMac,
length: 17,
upper: true,
digits: true,
special: true
include: '0123456789ABCDEF',
pattern: 'XX-'
},
{
name: 'Hash128',
title: Locale.genPresetHash128,
length: 32,
lower: true,
digits: true
include: '0123456789abcdef'
},
{
name: 'Hash256',
title: Locale.genPresetHash256,
length: 64,
lower: true,
digits: true
include: '0123456789abcdef'
}
];
},

View File

@ -138,6 +138,10 @@
"genPsAmbiguous": "Ambiguous symbols",
"genPsInclude": "Additional symbols to include",
"genPsExample": "Example of generated password",
"genPsPattern": "Pattern",
"genPsPatternHelp": "Patterns can be used to specify custom rules for selecting characters. For example, 1-AA will generate passwords starting with a digit, followed by a dash and two letters. You can use these symbols:",
"genPsAllRanges": "All symbols",
"genPsIncluded": "Additional symbols added above",
"keyChangeTitleRemote": "Master Key Changed",
"keyChangeMessageRemote": "Master key was changed for this database. Please enter a new key",

View File

@ -1,80 +1,65 @@
import kdbxweb from 'kdbxweb';
import { phonetic } from 'util/generators/phonetic';
const PasswordGenerator = {
charRanges: {
upper: 'ABCDEFGHJKLMNPQRSTUVWXYZ',
lower: 'abcdefghijkmnpqrstuvwxyz',
digits: '123456789',
special: '!@#$%^&*_+-=,./?;:`"~\'\\',
brackets: '(){}[]<>',
high:
'¡¢£¤¥¦§©ª«¬®¯°±²³´µ¶¹º»¼½¾¿ÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖ×ØÙÚÛÜÝÞßàáâãäåæçèéêëìíîïðñòóôõö÷øùúûüýþ',
ambiguous: 'O0oIl'
},
const CharRanges = {
upper: 'ABCDEFGHJKLMNPQRSTUVWXYZ',
lower: 'abcdefghijkmnpqrstuvwxyz',
digits: '123456789',
special: '!@#$%^&*_+-=,./?;:`"~\'\\',
brackets: '(){}[]<>',
high:
'¡¢£¤¥¦§©ª«¬®¯°±²³´µ¶¹º»¼½¾¿ÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖ×ØÙÚÛÜÝÞßàáâãäåæçèéêëìíîïðñòóôõö÷øùúûüýþ',
ambiguous: 'O0oIl'
};
const DefaultCharRangesByPattern = {
'A': CharRanges.upper,
'a': CharRanges.lower,
'1': CharRanges.digits,
'*': CharRanges.special,
'[': CharRanges.brackets,
'Ä': CharRanges.high,
'0': CharRanges.ambiguous
};
const PasswordGenerator = {
generate(opts) {
if (!opts || typeof opts.length !== 'number' || opts.length < 0) {
return '';
}
switch (opts.name) {
case 'Pronounceable':
return this.generatePronounceable(opts);
case 'Hash128':
return this.generateHash(32);
case 'Hash256':
return this.generateHash(64);
case 'Mac':
return this.generateMac();
if (opts.name === 'Pronounceable') {
return this.generatePronounceable(opts);
}
const ranges = Object.keys(this.charRanges)
const ranges = Object.keys(CharRanges)
.filter(r => opts[r])
.map(function(r) {
return this.charRanges[r];
}, this);
.map(r => CharRanges[r]);
if (opts.include && opts.include.length) {
ranges.push(opts.include);
}
if (!ranges.length) {
return '';
}
const pool = ranges.join('');
const rangesByPatternChar = {
...DefaultCharRangesByPattern,
'X': ranges.join(''),
'I': opts.include || ''
};
const pattern = opts.pattern || 'X';
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];
chars.push(pool[rand % pool.length]);
const patternChar = pattern[i % pattern.length];
const range = rangesByPatternChar[patternChar];
const char = range ? range[rand % range.length] : patternChar;
chars.push(char);
}
return chars.join('');
},
generateMac() {
const segmentsCount = 6;
const randomBytes = kdbxweb.Random.getBytes(segmentsCount);
let result = '';
for (let i = 0; i < segmentsCount; i++) {
let segment = randomBytes[i].toString(16).toUpperCase();
if (segment.length < 2) {
segment = '0' + segment;
}
result += (result ? '-' : '') + segment;
}
return result;
},
generateHash(length) {
const randomBytes = kdbxweb.Random.getBytes(length);
let result = '';
for (let i = 0; i < length; i++) {
result += randomBytes[i].toString(16)[0];
}
return result;
},
generatePronounceable(opts) {
const pass = phonetic.generate({
length: opts.length,
seed: this.generateHash(1024)
length: opts.length
});
let result = '';
const upper = [];
@ -98,7 +83,7 @@ const PasswordGenerator = {
const opts = {};
let length = 0;
if (password) {
const charRanges = this.charRanges;
const charRanges = CharRanges;
password.forEachChar(ch => {
length++;
ch = String.fromCharCode(ch);
@ -114,4 +99,4 @@ const PasswordGenerator = {
}
};
export { PasswordGenerator };
export { PasswordGenerator, CharRanges };

View File

@ -1,7 +1,7 @@
import { Events } from 'framework/events';
import { View } from 'framework/views/view';
import { GeneratorPresets } from 'comp/app/generator-presets';
import { PasswordGenerator } from 'util/generators/password-generator';
import { PasswordGenerator, CharRanges } from 'util/generators/password-generator';
import { Locale } from 'util/locale';
import { Scrollable } from 'framework/views/scrollable';
import template from 'templates/generator-presets.hbs';
@ -16,12 +16,14 @@ class GeneratorPresetsView extends View {
'change .gen-ps__list': 'changePreset',
'click .gen-ps__btn-create': 'createPreset',
'click .gen-ps__btn-delete': 'deletePreset',
'click .info-btn--pattern': 'togglePatternHelp',
'input #gen-ps__field-title': 'changeTitle',
'change #gen-ps__check-enabled': 'changeEnabled',
'change #gen-ps__check-default': 'changeDefault',
'input #gen-ps__field-length': 'changeLength',
'change .gen-ps__check-range': 'changeRange',
'input #gen-ps__field-include': 'changeInclude'
'input #gen-ps__field-include': 'changeInclude',
'input #gen-ps__field-pattern': 'changePattern'
};
selected = null;
@ -65,7 +67,7 @@ class GeneratorPresetsView extends View {
name: nameLower,
title: Locale['genPs' + name],
enabled: sel[nameLower],
sample: rangeOverride[nameLower] || PasswordGenerator.charRanges[nameLower]
sample: rangeOverride[nameLower] || CharRanges[nameLower]
};
}
);
@ -119,6 +121,10 @@ class GeneratorPresetsView extends View {
this.render();
}
togglePatternHelp() {
this.$el.find('.gen-ps__pattern-help').toggleClass('hide');
}
changeTitle(e) {
const title = $.trim(e.target.value);
if (title && title !== this.getPreset(this.selected).title) {
@ -175,6 +181,15 @@ class GeneratorPresetsView extends View {
this.presets = GeneratorPresets.all;
this.renderExample();
}
changePattern(e) {
const pattern = e.target.value;
if (pattern !== this.getPreset(this.selected).pattern) {
GeneratorPresets.setPreset(this.selected, { pattern });
}
this.presets = GeneratorPresets.all;
this.renderExample();
}
}
Object.assign(GeneratorPresetsView.prototype, Scrollable);

View File

@ -38,4 +38,8 @@
&__input {
height: 2em;
}
&__pattern-help {
margin-bottom: $base-padding-v;
}
}

View File

@ -13,6 +13,7 @@ $fa-font-path: '~font-awesome/fonts';
@import 'utils/common-dropdown';
@import 'utils/auto-type-hint';
@import 'utils/drag';
@import 'utils/help';
@import 'utils/selection';
@import 'common/dates';

View File

@ -0,0 +1,7 @@
.info-btn {
cursor: pointer;
color: var(--muted-color);
&:hover {
color: var(--text-color);
}
}

View File

@ -41,6 +41,25 @@
<input type="text" class="input-base" id="gen-ps__field-include" value="{{selected.include}}"
{{#if selected.builtIn}}readonly{{/if}} />
</div>
<div class="gen-ps__field">
<label for="gen-ps__field-pattern">{{res 'genPsPattern'}}: <i class="fa fa-info-circle info-btn info-btn--pattern"></i></label>
<div class="gen-ps__pattern-help hide">
<p>{{res 'genPsPatternHelp'}}</p>
<p>
<code>X</code> {{res 'genPsAllRanges'}}<br/>
<code>A</code> {{res 'genPsUpper'}}<br/>
<code>a</code> {{res 'genPsLower'}}<br/>
<code>1</code> {{res 'genPsDigits'}}<br/>
<code>*</code> {{res 'genPsSpecial'}}<br/>
<code>[</code> {{res 'genPsBrackets'}}<br/>
<code>Ä</code> {{res 'genPsHigh'}}<br/>
<code>0</code> {{res 'genPsAmbiguous'}}<br/>
<code>I</code> {{res 'genPsIncluded'}}
</p>
</div>
<input type="text" class="input-base" id="gen-ps__field-pattern" value="{{selected.pattern}}"
{{#if selected.builtIn}}readonly{{/if}} />
</div>
<div class="gen-ps__field">
<label>{{res 'genPsExample'}}:</label>
<div class="gen-ps__example"></div>

View File

@ -5,6 +5,7 @@ Release notes
`+` #1065: PORTABLE_EXECUTABLE_DIR environment variable
`*` #1397: Segoe UI font on Windows
`+` #1393: option to disable saving and exporting (canSaveTo)
`+` #1006: password generator patterns
`-` fix #1396: fixed hyperlinks in notes
`-` fix #1323: version in the About dialog
`-` fix #734: OTP secrets with spaces