mirror of
https://github.com/keeweb/keeweb.git
synced 2024-06-26 07:39:04 +02:00
generator presets
This commit is contained in:
parent
ef23e91d34
commit
ab76e3f90d
|
@ -51,6 +51,16 @@ var Locale = {
|
||||||
footerTitleLock: 'Lock',
|
footerTitleLock: 'Lock',
|
||||||
|
|
||||||
genLen: 'Length',
|
genLen: 'Length',
|
||||||
|
genPresetDefault: 'default preset',
|
||||||
|
genPresetDerived: 'like old password',
|
||||||
|
genPresetPronounceable: 'pronounceable',
|
||||||
|
genPresetMed: 'medium length',
|
||||||
|
genPresetLong: 'long',
|
||||||
|
genPresetPin4: '4-digit PIN',
|
||||||
|
genPresetMac: 'MAC address',
|
||||||
|
genPresetHash128: '128-bit hash',
|
||||||
|
genPresetHash256: '256-bit hash',
|
||||||
|
|
||||||
grpTitle: 'Group',
|
grpTitle: 'Group',
|
||||||
grpSearch: 'Enable searching entries in this group',
|
grpSearch: 'Enable searching entries in this group',
|
||||||
|
|
||||||
|
@ -243,7 +253,7 @@ var Locale = {
|
||||||
setFileHistory: 'History',
|
setFileHistory: 'History',
|
||||||
setFileEnableTrash: 'Enable trash',
|
setFileEnableTrash: 'Enable trash',
|
||||||
setFileHistLen: 'History length, keep last records per entry',
|
setFileHistLen: 'History length, keep last records per entry',
|
||||||
resFileHistSize: 'History size, total MB per file',
|
setFileHistSize: 'History size, total MB per file',
|
||||||
setFileAdvanced: 'Advanced',
|
setFileAdvanced: 'Advanced',
|
||||||
setFileRounds: 'Key encryption rounds',
|
setFileRounds: 'Key encryption rounds',
|
||||||
setFileUseKeyFile: 'Use key file',
|
setFileUseKeyFile: 'Use key file',
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
var kdbxweb = require('kdbxweb');
|
var kdbxweb = require('kdbxweb'),
|
||||||
|
phonetic = require('./phonetic');
|
||||||
|
|
||||||
var PasswordGenerator = {
|
var PasswordGenerator = {
|
||||||
charRanges: {
|
charRanges: {
|
||||||
|
@ -12,10 +13,21 @@ var PasswordGenerator = {
|
||||||
high: '¡¢£¤¥¦§©ª«¬®¯°±²³´µ¶¹º»¼½¾¿ÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖ×ØÙÚÛÜÝÞßàáâãäåæçèéêëìíîïðñòóôõö÷øùúûüýþ',
|
high: '¡¢£¤¥¦§©ª«¬®¯°±²³´µ¶¹º»¼½¾¿ÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖ×ØÙÚÛÜÝÞßàáâãäåæçèéêëìíîïðñòóôõö÷øùúûüýþ',
|
||||||
ambiguous: 'O0oIl'
|
ambiguous: 'O0oIl'
|
||||||
},
|
},
|
||||||
|
|
||||||
generate: function(opts) {
|
generate: function(opts) {
|
||||||
if (!opts || typeof opts.length !== 'number' || opts.length < 0) {
|
if (!opts || typeof opts.length !== 'number' || opts.length < 0) {
|
||||||
return '';
|
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();
|
||||||
|
}
|
||||||
var ranges = Object.keys(this.charRanges)
|
var ranges = Object.keys(this.charRanges)
|
||||||
.filter(function(r) { return opts[r]; })
|
.filter(function(r) { return opts[r]; })
|
||||||
.map(function(r) { return this.charRanges[r]; }, this);
|
.map(function(r) { return this.charRanges[r]; }, this);
|
||||||
|
@ -31,6 +43,69 @@ var PasswordGenerator = {
|
||||||
}
|
}
|
||||||
return _.shuffle(chars).join('');
|
return _.shuffle(chars).join('');
|
||||||
},
|
},
|
||||||
|
|
||||||
|
generateMac: function() {
|
||||||
|
var segmentsCount = 6;
|
||||||
|
var randomBytes = kdbxweb.Random.getBytes(segmentsCount);
|
||||||
|
var result = '';
|
||||||
|
for (var i = 0; i < segmentsCount; i++) {
|
||||||
|
var segment = randomBytes[i].toString(16).toUpperCase();
|
||||||
|
if (segment.length < 2) {
|
||||||
|
segment = '0' + segment;
|
||||||
|
}
|
||||||
|
result += (result ? '-' : '') + segment;
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
},
|
||||||
|
|
||||||
|
generateHash: function(length) {
|
||||||
|
var randomBytes = kdbxweb.Random.getBytes(length);
|
||||||
|
var result = '';
|
||||||
|
for (var i = 0; i < length; i++) {
|
||||||
|
result += randomBytes[i].toString(16)[0];
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
},
|
||||||
|
|
||||||
|
generatePronounceable: function(opts) {
|
||||||
|
var pass = phonetic.generate({ length: opts.length });
|
||||||
|
var result = '';
|
||||||
|
var upper = [];
|
||||||
|
var i;
|
||||||
|
if (opts.upper) {
|
||||||
|
for (i = 0; i < pass.length; i += 8) {
|
||||||
|
upper.push(Math.floor(Math.random() * opts.length));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (i = 0; i < pass.length; i++) {
|
||||||
|
var ch = pass[i];
|
||||||
|
if (upper.indexOf(i) >= 0) {
|
||||||
|
ch = ch.toUpperCase();
|
||||||
|
}
|
||||||
|
result += ch;
|
||||||
|
}
|
||||||
|
return result.substr(0, opts.length);
|
||||||
|
},
|
||||||
|
|
||||||
|
deriveOpts: function(password) {
|
||||||
|
var opts = {};
|
||||||
|
var length = 0;
|
||||||
|
if (password) {
|
||||||
|
var charRanges = this.charRanges;
|
||||||
|
password.forEachChar(function(ch) {
|
||||||
|
length++;
|
||||||
|
ch = String.fromCharCode(ch);
|
||||||
|
_.forEach(charRanges, function(chars, range) {
|
||||||
|
if (chars.indexOf(ch) >= 0) {
|
||||||
|
opts[range] = true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
opts.length = length;
|
||||||
|
return opts;
|
||||||
|
},
|
||||||
|
|
||||||
present: function(length) {
|
present: function(length) {
|
||||||
return new Array(length + 1).join('•');
|
return new Array(length + 1).join('•');
|
||||||
}
|
}
|
||||||
|
|
258
app/scripts/util/phonetic.js
Normal file
258
app/scripts/util/phonetic.js
Normal file
|
@ -0,0 +1,258 @@
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Phonetic
|
||||||
|
* Copyright 2013 Tom Frost
|
||||||
|
*/
|
||||||
|
|
||||||
|
// removed node.js deps, making it available to load in browser
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Phonetics that sound best before a vowel.
|
||||||
|
* @type {Array}
|
||||||
|
*/
|
||||||
|
var PHONETIC_PRE = [
|
||||||
|
// Simple phonetics
|
||||||
|
'b', 'c', 'd', 'f', 'g', 'h', 'j', 'k', 'l', 'm', 'n', 'p',
|
||||||
|
'qu', 'r', 's', 't',
|
||||||
|
// Complex phonetics
|
||||||
|
'bl',
|
||||||
|
'ch', 'cl', 'cr',
|
||||||
|
'dr',
|
||||||
|
'fl', 'fr',
|
||||||
|
'gl', 'gr',
|
||||||
|
'kl', 'kr',
|
||||||
|
'ph', 'pr', 'pl',
|
||||||
|
'sc', 'sh', 'sl', 'sn', 'sr', 'st', 'str', 'sw',
|
||||||
|
'th', 'tr',
|
||||||
|
'br',
|
||||||
|
'v', 'w', 'y', 'z'
|
||||||
|
];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The number of simple phonetics within the 'pre' set.
|
||||||
|
* @type {number}
|
||||||
|
*/
|
||||||
|
var PHONETIC_PRE_SIMPLE_LENGTH = 16;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Vowel sound phonetics.
|
||||||
|
* @type {Array}
|
||||||
|
*/
|
||||||
|
var PHONETIC_MID = [
|
||||||
|
// Simple phonetics
|
||||||
|
'a', 'e', 'i', 'o', 'u',
|
||||||
|
// Complex phonetics
|
||||||
|
'ee', 'ie', 'oo', 'ou', 'ue'
|
||||||
|
];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The number of simple phonetics within the 'mid' set.
|
||||||
|
* @type {number}
|
||||||
|
*/
|
||||||
|
var PHONETIC_MID_SIMPLE_LENGTH = 5;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Phonetics that sound best after a vowel.
|
||||||
|
* @type {Array}
|
||||||
|
*/
|
||||||
|
var PHONETIC_POST = [
|
||||||
|
// Simple phonetics
|
||||||
|
'b', 'd', 'f', 'g', 'k', 'l', 'm', 'n', 'p', 'r', 's', 't', 'y',
|
||||||
|
// Complex phonetics
|
||||||
|
'ch', 'ck',
|
||||||
|
'ln',
|
||||||
|
'nk', 'ng',
|
||||||
|
'rn',
|
||||||
|
'sh', 'sk', 'st',
|
||||||
|
'th',
|
||||||
|
'x', 'z'
|
||||||
|
];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The number of simple phonetics within the 'post' set.
|
||||||
|
* @type {number}
|
||||||
|
*/
|
||||||
|
var PHONETIC_POST_SIMPLE_LENGTH = 13;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A mapping of regular expressions to replacements, which will be run on the
|
||||||
|
* resulting word before it gets returned. The purpose of replacements is to
|
||||||
|
* address language subtleties that the phonetic builder is incapable of
|
||||||
|
* understanding, such as 've' more pronounceable than just 'v' at the end of
|
||||||
|
* a word, 'ey' more pronounceable than 'iy', etc.
|
||||||
|
* @type {{}}
|
||||||
|
*/
|
||||||
|
var REPLACEMENTS = {
|
||||||
|
'quu': 'que',
|
||||||
|
'qu([aeiou]){2}': 'qu$1',
|
||||||
|
'[iu]y': 'ey',
|
||||||
|
'eye': 'ye',
|
||||||
|
'(.)ye$': '$1y',
|
||||||
|
'(^|e)cie(?!$)': '$1cei',
|
||||||
|
'([vz])$': '$1e',
|
||||||
|
'[iu]w': 'ow'
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds a single syllable to the word contained in the wordObj. A syllable
|
||||||
|
* contains, at maximum, a phonetic from each the PRE, MID, and POST phonetic
|
||||||
|
* sets. Some syllables will omit pre or post based on the
|
||||||
|
* options.compoundSimplicity.
|
||||||
|
*
|
||||||
|
* @param {{word, numeric, lastSkippedPre, lastSkippedPost, opts}} wordObj The
|
||||||
|
* word object on which to operate.
|
||||||
|
*/
|
||||||
|
function addSyllable(wordObj) {
|
||||||
|
var deriv = getDerivative(wordObj.numeric),
|
||||||
|
compound = deriv % wordObj.opts.compoundSimplicity === 0,
|
||||||
|
first = wordObj.word === '',
|
||||||
|
preOnFirst = deriv % 6 > 0;
|
||||||
|
if ((first && preOnFirst) || wordObj.lastSkippedPost || compound) {
|
||||||
|
wordObj.word += getNextPhonetic(PHONETIC_PRE,
|
||||||
|
PHONETIC_PRE_SIMPLE_LENGTH, wordObj);
|
||||||
|
wordObj.lastSkippedPre = false;
|
||||||
|
} else {
|
||||||
|
wordObj.lastSkippedPre = true;
|
||||||
|
}
|
||||||
|
wordObj.word += getNextPhonetic(PHONETIC_MID, PHONETIC_MID_SIMPLE_LENGTH,
|
||||||
|
wordObj, first && wordObj.lastSkippedPre);
|
||||||
|
if (wordObj.lastSkippedPre || compound) {
|
||||||
|
wordObj.word += getNextPhonetic(PHONETIC_POST,
|
||||||
|
PHONETIC_POST_SIMPLE_LENGTH, wordObj);
|
||||||
|
wordObj.lastSkippedPost = false;
|
||||||
|
} else {
|
||||||
|
wordObj.lastSkippedPost = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets a derivative of a number by repeatedly dividing it by 7 and adding the
|
||||||
|
* remainders together. It's useful to base decisions on a derivative rather
|
||||||
|
* than the wordObj's current numeric, as it avoids making the same decisions
|
||||||
|
* around the same phonetics.
|
||||||
|
*
|
||||||
|
* @param {number} num A number from which a derivative should be calculated
|
||||||
|
* @returns {number} The derivative.
|
||||||
|
*/
|
||||||
|
function getDerivative(num) {
|
||||||
|
var derivative = 1;
|
||||||
|
while (num) {
|
||||||
|
derivative += num % 7;
|
||||||
|
num = Math.floor(num / 7);
|
||||||
|
}
|
||||||
|
return derivative;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Combines the option defaults with the provided overrides. Available
|
||||||
|
* options are:
|
||||||
|
* - seed: A string or number with which to seed the generator. Using the
|
||||||
|
* same seed (with the same other options) will coerce the generator
|
||||||
|
* into producing the same word. Default is random.
|
||||||
|
* - phoneticSimplicity: The greater this number, the simpler the phonetics.
|
||||||
|
* For example, 1 might produce 'str' while 5 might produce 's' for
|
||||||
|
* the same syllable. Minimum is 1, default is 5.
|
||||||
|
* - compoundSimplicity: The greater this number, the less likely the
|
||||||
|
* resulting word will sound "compound", such as "ripkuth" instead of
|
||||||
|
* "riputh". Minimum is 1, default is 5.
|
||||||
|
*
|
||||||
|
* @param {{}} overrides A set of options and values with which to override
|
||||||
|
* the defaults.
|
||||||
|
* @returns {{seed, phoneticSimplicity, compoundSimplicity}}
|
||||||
|
* An options object.
|
||||||
|
*/
|
||||||
|
function getOptions(overrides) {
|
||||||
|
var options = {};
|
||||||
|
overrides = overrides || {};
|
||||||
|
options.length = overrides.length || 16;
|
||||||
|
options.seed = overrides.seed || Math.random();
|
||||||
|
options.phoneticSimplicity = overrides.phoneticSimplicity ?
|
||||||
|
Math.max(overrides.phoneticSimplicity, 1) : 5;
|
||||||
|
options.compoundSimplicity = overrides.compoundSimplicity ?
|
||||||
|
Math.max(overrides.compoundSimplicity, 1) : 5;
|
||||||
|
return options;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the next pseudo-random phonetic from a given phonetic set,
|
||||||
|
* intelligently determining whether to include "complex" phonetics in that
|
||||||
|
* set based on the options.phoneticSimplicity.
|
||||||
|
*
|
||||||
|
* @param {Array} phoneticSet The array of phonetics from which to choose
|
||||||
|
* @param {number} simpleCap The number of 'simple' phonetics at the beginning
|
||||||
|
* of the phoneticSet
|
||||||
|
* @param {{word, numeric, lastSkippedPre, lastSkippedPost, opts}} wordObj The
|
||||||
|
* wordObj for which the phonetic is being chosen
|
||||||
|
* @param {boolean} [forceSimple] true to force a simple phonetic to be
|
||||||
|
* chosen; otherwise, the function will choose whether to include complex
|
||||||
|
* phonetics based on the derivative of wordObj.numeric.
|
||||||
|
* @returns {string} The chosen phonetic.
|
||||||
|
*/
|
||||||
|
function getNextPhonetic(phoneticSet, simpleCap, wordObj, forceSimple) {
|
||||||
|
var deriv = getDerivative(wordObj.numeric),
|
||||||
|
simple = (wordObj.numeric + deriv) % wordObj.opts.phoneticSimplicity > 0,
|
||||||
|
cap = simple || forceSimple ? simpleCap : phoneticSet.length,
|
||||||
|
phonetic = phoneticSet[wordObj.numeric % cap];
|
||||||
|
wordObj.numeric = getNumericHash(wordObj.numeric + wordObj.word);
|
||||||
|
return phonetic;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generates a numeric hash based on the input data. The hash is an md5, with
|
||||||
|
* each block of 32 bits converted to an integer and added together.
|
||||||
|
*
|
||||||
|
* @param {string|number} data The string or number to be hashed.
|
||||||
|
* @returns {number}
|
||||||
|
*/
|
||||||
|
function getNumericHash(data) {
|
||||||
|
var numeric = 0;
|
||||||
|
data += '-Phonetic';
|
||||||
|
for (var i = 0; i < data.length; i++) {
|
||||||
|
numeric += data.charCodeAt(i);
|
||||||
|
}
|
||||||
|
return numeric;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Applies post-processing to a word after it has already been generated. In
|
||||||
|
* this phase, the REPLACEMENTS are executed, applying language intelligence
|
||||||
|
* that can make generated words more pronounceable. The first letter is
|
||||||
|
* also capitalized.
|
||||||
|
*
|
||||||
|
* @param {{word, numeric, lastSkippedPre, lastSkippedPost, opts}} wordObj The
|
||||||
|
* word object to be processed.
|
||||||
|
* @returns {string} The processed word.
|
||||||
|
*/
|
||||||
|
function postProcess(wordObj) {
|
||||||
|
var regex;
|
||||||
|
for (var i in REPLACEMENTS) {
|
||||||
|
if (REPLACEMENTS.hasOwnProperty(i)) {
|
||||||
|
regex = new RegExp(i);
|
||||||
|
wordObj.word = wordObj.word.replace(regex, REPLACEMENTS[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return wordObj.word;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generates a new word based on the given options. For available options,
|
||||||
|
* see getOptions.
|
||||||
|
*
|
||||||
|
* @param {*} [options] A collection of options to control the word generator.
|
||||||
|
* @returns {string} A generated word.
|
||||||
|
*/
|
||||||
|
module.exports.generate = function(options) {
|
||||||
|
options = getOptions(options);
|
||||||
|
var length = options.length,
|
||||||
|
wordObj = {
|
||||||
|
numeric: getNumericHash(options.seed),
|
||||||
|
lastSkippedPost: false,
|
||||||
|
word: '',
|
||||||
|
opts: options
|
||||||
|
};
|
||||||
|
while (wordObj.word.length < length) {
|
||||||
|
addSyllable(wordObj);
|
||||||
|
}
|
||||||
|
return postProcess(wordObj).substr(0, length);
|
||||||
|
};
|
|
@ -56,7 +56,7 @@ var FieldViewText = FieldView.extend({
|
||||||
this.hideGenerator();
|
this.hideGenerator();
|
||||||
} else {
|
} else {
|
||||||
var fieldRect = this.input[0].getBoundingClientRect();
|
var fieldRect = this.input[0].getBoundingClientRect();
|
||||||
this.gen = new GeneratorView({model: {pos: {left: fieldRect.left, top: fieldRect.bottom}}}).render();
|
this.gen = new GeneratorView({model: {pos: {left: fieldRect.left, top: fieldRect.bottom}, password: this.value}}).render();
|
||||||
this.gen.once('remove', this.generatorClosed.bind(this));
|
this.gen.once('remove', this.generatorClosed.bind(this));
|
||||||
this.gen.once('result', this.generatorResult.bind(this));
|
this.gen.once('result', this.generatorResult.bind(this));
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,10 +5,6 @@ var Backbone = require('backbone'),
|
||||||
CopyPaste = require('../comp/copy-paste'),
|
CopyPaste = require('../comp/copy-paste'),
|
||||||
Locale = require('../util/locale');
|
Locale = require('../util/locale');
|
||||||
|
|
||||||
var DefaultGenOpts = {
|
|
||||||
length: 16, upper: true, lower: true, digits: true, special: false, brackets: false, high: false, ambiguous: false
|
|
||||||
};
|
|
||||||
|
|
||||||
var GeneratorView = Backbone.View.extend({
|
var GeneratorView = Backbone.View.extend({
|
||||||
el: 'body',
|
el: 'body',
|
||||||
|
|
||||||
|
@ -20,26 +16,67 @@ var GeneratorView = Backbone.View.extend({
|
||||||
'mousemove .gen__length-range': 'lengthChange',
|
'mousemove .gen__length-range': 'lengthChange',
|
||||||
'change .gen__length-range': 'lengthChange',
|
'change .gen__length-range': 'lengthChange',
|
||||||
'change .gen__check input[type=checkbox]': 'checkChange',
|
'change .gen__check input[type=checkbox]': 'checkChange',
|
||||||
'click .gen__btn-ok': 'btnOkClick'
|
'click .gen__btn-ok': 'btnOkClick',
|
||||||
|
'change .gen__sel-tpl': 'templateChange'
|
||||||
},
|
},
|
||||||
|
|
||||||
valuesMap: [3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,22,24,26,28,30,32,48,64],
|
valuesMap: [3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,22,24,26,28,30,32,48,64],
|
||||||
|
|
||||||
|
presets: null,
|
||||||
|
preset: null,
|
||||||
|
|
||||||
initialize: function () {
|
initialize: function () {
|
||||||
|
this.createPresets();
|
||||||
|
var preset = this.preset;
|
||||||
|
this.gen = _.clone(_.find(this.presets, function(pr) { return pr.name === preset; }));
|
||||||
|
this.gen = _.clone(this.presets[1]);
|
||||||
$('body').one('click', this.remove.bind(this));
|
$('body').one('click', this.remove.bind(this));
|
||||||
this.gen = _.clone(DefaultGenOpts);
|
|
||||||
},
|
},
|
||||||
|
|
||||||
render: function() {
|
render: function() {
|
||||||
var canCopy = document.queryCommandSupported('copy');
|
var canCopy = document.queryCommandSupported('copy');
|
||||||
var btnTitle = this.model.copy ? canCopy ? Locale.alertCopy : Locale.alertClose : Locale.alertOk;
|
var btnTitle = this.model.copy ? canCopy ? Locale.alertCopy : Locale.alertClose : Locale.alertOk;
|
||||||
this.renderTemplate({ btnTitle: btnTitle, opt: this.gen });
|
this.renderTemplate({ btnTitle: btnTitle, opt: this.gen, presets: this.presets, preset: this.preset });
|
||||||
this.resultEl = this.$el.find('.gen__result');
|
this.resultEl = this.$el.find('.gen__result');
|
||||||
this.$el.css(this.model.pos);
|
this.$el.css(this.model.pos);
|
||||||
this.generate();
|
this.generate();
|
||||||
return this;
|
return this;
|
||||||
},
|
},
|
||||||
|
|
||||||
|
createPresets: function() {
|
||||||
|
this.presets = [
|
||||||
|
{ name: 'Default', length: 16, upper: true, lower: true, digits: true },
|
||||||
|
{ name: 'Pronounceable', length: 10, lower: true, upper: true },
|
||||||
|
{ name: 'Med', length: 16, upper: true, lower: true, digits: true, special: true, brackets: true, ambiguous: true },
|
||||||
|
{ name: 'Long', length: 32, upper: true, lower: true, digits: true },
|
||||||
|
{ name: 'Pin4', length: 4, digits: true },
|
||||||
|
{ name: 'Mac', length: 17, upper: true, digits: true, special: true },
|
||||||
|
{ name: 'Hash128', length: 32, lower: true, digits: true },
|
||||||
|
{ name: 'Hash256', length: 64, lower: true, digits: true }
|
||||||
|
];
|
||||||
|
if (this.model.password) {
|
||||||
|
var derivedPreset = { name: 'Derived' };
|
||||||
|
_.extend(derivedPreset, PasswordGenerator.deriveOpts(this.model.password));
|
||||||
|
for (var i = 0; i < this.valuesMap.length; i++) {
|
||||||
|
if (this.valuesMap[i] >= derivedPreset.length) {
|
||||||
|
derivedPreset.length = this.valuesMap[i];
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (derivedPreset.length > this.valuesMap[this.valuesMap.length - 1]) {
|
||||||
|
derivedPreset.length = this.valuesMap[this.valuesMap.length - 1];
|
||||||
|
}
|
||||||
|
this.presets.splice(1, 0, derivedPreset);
|
||||||
|
this.preset = 'Derived';
|
||||||
|
} else {
|
||||||
|
this.preset = 'Default';
|
||||||
|
}
|
||||||
|
this.presets.forEach(function(pr) {
|
||||||
|
pr.pseudoLength = this.valuesMap.indexOf(pr.length);
|
||||||
|
pr.title = Locale['genPreset' + pr.name];
|
||||||
|
}, this);
|
||||||
|
},
|
||||||
|
|
||||||
click: function(e) {
|
click: function(e) {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
},
|
},
|
||||||
|
@ -49,6 +86,7 @@ var GeneratorView = Backbone.View.extend({
|
||||||
if (val !== this.gen.length) {
|
if (val !== this.gen.length) {
|
||||||
this.gen.length = val;
|
this.gen.length = val;
|
||||||
this.$el.find('.gen__length-range-val').html(val);
|
this.$el.find('.gen__length-range-val').html(val);
|
||||||
|
this.optionChanged('length');
|
||||||
this.generate();
|
this.generate();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -58,9 +96,19 @@ var GeneratorView = Backbone.View.extend({
|
||||||
if (id) {
|
if (id) {
|
||||||
this.gen[id] = e.target.checked;
|
this.gen[id] = e.target.checked;
|
||||||
}
|
}
|
||||||
|
this.optionChanged(id);
|
||||||
this.generate();
|
this.generate();
|
||||||
},
|
},
|
||||||
|
|
||||||
|
optionChanged: function(option) {
|
||||||
|
if (this.preset === 'Custom' ||
|
||||||
|
this.preset === 'Pronounceable' && ['length', 'lower', 'upper'].indexOf(option) >= 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.preset = this.gen.name = 'Custom';
|
||||||
|
this.$el.find('.gen__sel-tpl').val('');
|
||||||
|
},
|
||||||
|
|
||||||
generate: function() {
|
generate: function() {
|
||||||
this.password = PasswordGenerator.generate(this.gen);
|
this.password = PasswordGenerator.generate(this.gen);
|
||||||
this.resultEl.text(this.password);
|
this.resultEl.text(this.password);
|
||||||
|
@ -77,6 +125,14 @@ var GeneratorView = Backbone.View.extend({
|
||||||
CopyPaste.copy(this.password);
|
CopyPaste.copy(this.password);
|
||||||
this.trigger('result', this.password);
|
this.trigger('result', this.password);
|
||||||
this.remove();
|
this.remove();
|
||||||
|
},
|
||||||
|
|
||||||
|
templateChange: function(e) {
|
||||||
|
var name = e.target.value;
|
||||||
|
this.preset = name;
|
||||||
|
var preset = _.find(this.presets, function(t) { return t.name === name; });
|
||||||
|
this.gen = _.clone(preset);
|
||||||
|
this.render();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -5,6 +5,10 @@
|
||||||
width: 11em;
|
width: 11em;
|
||||||
&__length-range {
|
&__length-range {
|
||||||
}
|
}
|
||||||
|
&__sel-tpl {
|
||||||
|
width: 100%;
|
||||||
|
margin-top: $base-padding-v;
|
||||||
|
}
|
||||||
&__check {
|
&__check {
|
||||||
width: 40%;
|
width: 40%;
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
|
|
|
@ -1,6 +1,11 @@
|
||||||
<div class="gen">
|
<div class="gen">
|
||||||
<div>{{res 'genLen'}}: <span class="gen__length-range-val">{{opt.length}}</span></div>
|
<div>{{res 'genLen'}}: <span class="gen__length-range-val">{{opt.length}}</span></div>
|
||||||
<input type="range" class="gen__length-range" value="13" min="0" max="25" />
|
<select class="gen__sel-tpl input-base">
|
||||||
|
{{#each presets as |ps|}}
|
||||||
|
<option value="{{ps.name}}" {{#ifeq ps.name ../preset}}selected{{/ifeq}}>{{ps.title}}</option>
|
||||||
|
{{/each}}
|
||||||
|
</select>
|
||||||
|
<input type="range" class="gen__length-range" value="{{opt.pseudoLength}}" min="0" max="25" />
|
||||||
<div>
|
<div>
|
||||||
<div class="gen__check"><input type="checkbox" id="gen__check-upper"
|
<div class="gen__check"><input type="checkbox" id="gen__check-upper"
|
||||||
data-id="upper" {{#if opt.upper}}checked{{/if}}><label for="gen__check-upper">ABC</label></div>
|
data-id="upper" {{#if opt.upper}}checked{{/if}}><label for="gen__check-upper">ABC</label></div>
|
||||||
|
|
|
@ -30,6 +30,7 @@
|
||||||
<li><a href="https://github.com/Diokuz/baron" target="_blank">baron</a><span class="muted-color">, native scroll with custom scrollbar</span></li>
|
<li><a href="https://github.com/Diokuz/baron" target="_blank">baron</a><span class="muted-color">, native scroll with custom scrollbar</span></li>
|
||||||
<li><a href="http://dbushell.github.io/Pikaday/" target="_blank">pikaday</a><span class="muted-color">, a refreshing JavaScript datepicker</span></li>
|
<li><a href="http://dbushell.github.io/Pikaday/" target="_blank">pikaday</a><span class="muted-color">, a refreshing JavaScript datepicker</span></li>
|
||||||
<li><a href="https://github.com/eligrey/FileSaver.js" target="_blank">filesaver.js</a><span class="muted-color">, HTML5 saveAs FileSaver implementation</span></li>
|
<li><a href="https://github.com/eligrey/FileSaver.js" target="_blank">filesaver.js</a><span class="muted-color">, HTML5 saveAs FileSaver implementation</span></li>
|
||||||
|
<li><a href="https://github.com/TomFrost/node-phonetic" target="_blank">node-phonetic</a><span class="muted-color">, generates unique, pronounceable names</span></li>
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
<h3>Desktop modules</h3>
|
<h3>Desktop modules</h3>
|
||||||
|
|
|
@ -50,7 +50,7 @@
|
||||||
</div>
|
</div>
|
||||||
<label for="settings__file-hist-len">{{res 'setFileHistLen'}}:</label>
|
<label for="settings__file-hist-len">{{res 'setFileHistLen'}}:</label>
|
||||||
<input type="text" pattern="\d+" required class="settings__input input-base" id="settings__file-hist-len" value="{{historyMaxItems}}" />
|
<input type="text" pattern="\d+" required class="settings__input input-base" id="settings__file-hist-len" value="{{historyMaxItems}}" />
|
||||||
<label for="settings__file-hist-size">{{res 'resFileHistSize'}}:</label>
|
<label for="settings__file-hist-size">{{res 'setFileHistSize'}}:</label>
|
||||||
<input type="text" pattern="\d+" required class="settings__input input-base" id="settings__file-hist-size" value="{{historyMaxSize}}" />
|
<input type="text" pattern="\d+" required class="settings__input input-base" id="settings__file-hist-size" value="{{historyMaxSize}}" />
|
||||||
|
|
||||||
<h2>{{res 'setFileAdvanced'}}</h2>
|
<h2>{{res 'setFileAdvanced'}}</h2>
|
||||||
|
|
Loading…
Reference in New Issue
Block a user