1
0
mirror of http://git.whoc.org.uk/git/password-manager.git synced 2025-10-29 10:27:35 +01:00

Added password generator for password fields

This commit is contained in:
Giulio Cesare Solaroli
2015-08-08 11:51:21 +02:00
parent c99e8bcd71
commit 2e6ae458de
9 changed files with 824 additions and 676 deletions

View File

@@ -320,7 +320,6 @@ MochiKit.Base.update(Clipperz.PM.Crypto, {
deferredResult.addCallback(MochiKit.Async.wait, 0.1);
deferredResult.addCallback(Clipperz.Base.evalJSON);
deferredResult.addErrback(function(anError) {
console.log("PIPPO_1", anError)
Clipperz.logError("Error while decrypting data [4]");
throw Clipperz.Crypto.Base.exception.CorruptedMessage;
})
@@ -403,7 +402,6 @@ console.log("PIPPO_1", anError)
try {
result = Clipperz.Base.evalJSON(value);
} catch (exception) {
console.log("PIPPO_2", anError)
Clipperz.logError("Error while decrypting data [4]");
throw Clipperz.Crypto.Base.exception.CorruptedMessage;
}

View File

@@ -37,6 +37,7 @@ Clipperz.PM.UI.Components.Cards.EditClass = React.createClass({
getInitialState: function() {
return {
'draggedFieldReference': null,
'passwordGeneratorFieldReference': null,
'fromFieldPosition': -1,
'toFieldPosition': -1,
'dropPosition': -1,
@@ -216,6 +217,19 @@ console.log("DROP"); //, anEvent);
//============================================================================
setValueFromPasswordGenerator: function (aField, aTextAreaRef) {
var reference = this.props['_reference'];
var self = this;
return function (aValue) {
aField.setValue(aValue);
React.findDOMNode(self.refs[aTextAreaRef]).value = aValue;
self.setState({'passwordGeneratorFieldReference':null});
MochiKit.Signal.signal(Clipperz.Signal.NotificationCenter, 'refreshCardEditDetail', reference);
};
},
handleChange: function (anObject , aMethodName) {
var reference = this.props['_reference'];
var method = MochiKit.Base.method(anObject, aMethodName);
@@ -270,6 +284,23 @@ console.log("DROP"); //, anEvent);
MochiKit.Signal.signal(Clipperz.Signal.NotificationCenter, 'refreshCardEditDetail', reference);
},
showPasswordGenerator: function (aField) {
var result;
if (aField['actionType'] == 'PASSWORD') {
var reference = this.props['_reference'];
var self = this;
result = function (anEvent) {
self.setState({'passwordGeneratorFieldReference':aField['_reference']});
};
} else {
result = null;
}
return result;
},
toggleLock: function (aField) {
var reference = this.props['_reference'];
@@ -290,6 +321,10 @@ console.log("DROP"); //, anEvent);
};
},
closePasswordGenerator: function () {
this.setState({'passwordGeneratorFieldReference': null});
},
//============================================================================
renderLabel: function (aLabel) {
@@ -320,6 +355,7 @@ console.log("DROP"); //, anEvent);
var cardFieldClasses = {};
var cardFieldValueClasses = {};
var field = aField['_field'];
var fieldValueRef = ref + '_textarea';
//console.log("RENDER FIELD", aField);
cardFieldClasses['cardField'] = true;
@@ -355,12 +391,12 @@ console.log("DROP"); //, anEvent);
React.DOM.input({'_className_':'_fieldLabel_', 'onChange':this.handleChange(field, 'setLabel'), 'defaultValue':aField['label'], 'placeholder': "label"}),
]),
React.DOM.div({'className':'fieldValue'}, [
// React.DOM.textarea({'className':Clipperz.PM.UI.Components.classNames(cardFieldValueClasses), 'onChange':this.handleChange(field, 'setValue'), 'defaultValue':aField['value'], 'placeholder': "value"}),
Clipperz.PM.UI.Components.Cards.TextArea({'className':Clipperz.PM.UI.Components.classNames(cardFieldValueClasses), 'onChange':this.handleChange(field, 'setValue'), 'onKeyDown':this.handleKeyDown(field), 'defaultValue':aField['value'], 'placeholder': "value"}),
(ref == this.state['passwordGeneratorFieldReference']) ? Clipperz.PM.UI.Components.Cards.PasswordGenerator({'field':aField, 'setValueCallback':this.setValueFromPasswordGenerator(field, fieldValueRef), 'closeClallback':this.closePasswordGenerator}) : null,
Clipperz.PM.UI.Components.Cards.TextArea({'className':Clipperz.PM.UI.Components.classNames(cardFieldValueClasses), 'onChange':this.handleChange(field, 'setValue'), 'onKeyDown':this.handleKeyDown(field), 'defaultValue':aField['value'], 'placeholder': "value", 'ref':fieldValueRef}),
])
]),
React.DOM.div({'className':'fieldAction'}, [
React.DOM.span({'className':'action'}, aField['actionType'].toLowerCase() == 'password' ? 'generate password' : aField['actionType'].toLowerCase()),
React.DOM.span({'className':'action ' + aField['actionType'], 'onClick':this.showPasswordGenerator(aField)}, aField['actionType'].toLowerCase() == 'password' ? 'password generator' : aField['actionType'].toLowerCase()),
React.DOM.span({'className':'toggleLock', 'onClick':this.toggleLock(field)}, aField['isHidden'] ? "locked" : "unlocked")
])
]);

View File

@@ -0,0 +1,220 @@
/*
Copyright 2008-2015 Clipperz Srl
This file is part of Clipperz, the online password manager.
For further information about its features and functionalities please
refer to http://www.clipperz.com.
* Clipperz is free software: you can redistribute it and/or modify it
under the terms of the GNU Affero General Public License as published
by the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
* Clipperz is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
See the GNU Affero General Public License for more details.
* You should have received a copy of the GNU Affero General Public
License along with Clipperz. If not, see http://www.gnu.org/licenses/.
*/
'use strict';
Clipperz.Base.module('Clipperz.PM.UI.Components.Cards');
Clipperz.PM.UI.Components.Cards.PasswordGeneratorClass = React.createClass({
charsetBlocks: {
'chars_AZ': 'ABCDEFGHIJKLMNOPQRSTUVWXYZ',
'chars_az': 'abcdefghijklmnopqrstuvwxyz',
'chars_09': '0123456789',
'chars_space': ' ',
'chars_other': '~`!@#$%^&*()-_=+,.<>/?[]{}\\|:;\'"'
},
gradientColors: ['#ff3236', '#e74030', '#cf4e2a', '#b75c24', '#9f6a1e', '#877818', '#6f8612', '#57940c', '#3fa206', '#25ad00', '#25ad00'],
getInitialState: function () {
return {
'length': 32,
'options': 'closed',
'chars_AZ': true,
'chars_az': true,
'chars_09': true,
'chars_space': false,
'chars_other': true,
'charset': '',
'password': '',
'entropy': 0,
};
},
setPasswordValue: function () {
this.props['setValueCallback'](this.state['password']);
},
toggleOptions: function () {
var options;
options = (this.state['options'] == 'closed') ? 'open' : 'closed';
this.setState({'options':options});
},
updateCharset: function () {
var charsetKeys = ['chars_AZ', 'chars_az', 'chars_09', 'chars_space', 'chars_other'];
var self = this;
var charset;
charset = MochiKit.Iter.reduce(function (acc, key) { return acc + self.charsetBlocks[key]}, MochiKit.Base.filter(function (key) { return self.state[key] == true;}, charsetKeys), '');
this.setState({'charset': charset});
MochiKit.Async.callLater(0.1, this.refreshPasswordValue);
},
refreshPasswordValue: function () {
var charsetBitSize;
var passwordString;
var charset;
var passwordLength;
var randomBytes;
var blockIndex;
charset = this.state['charset'];
passwordLength = this.state['length'];
charsetBitSize = 0;
randomBytes = Clipperz.Crypto.PRNG.defaultRandomGenerator().getRandomBytes(50);
blockIndex = 0;
passwordString = "";
while (Math.pow(2, charsetBitSize) < charset.length) {
charsetBitSize ++;
}
if (charsetBitSize > 0) {
// while (Clipperz.PM.Crypto.passwordEntropy(passwordString) < 128) {
while (passwordString.length < passwordLength) {
var randomValue;
if (((blockIndex + 1)*charsetBitSize) > (randomBytes.length() * 8)) {
randomBytes = Clipperz.Crypto.PRNG.defaultRandomGenerator().getRandomBytes(50);
blockIndex = 0;
}
randomValue = randomBytes.bitBlockAtIndexWithSize(blockIndex*charsetBitSize, charsetBitSize);
if (randomValue < charset.length) {
passwordString += charset.charAt(randomValue);
}
blockIndex ++;
}
} else {
passwordString = "";
}
this.setState({
'password': passwordString,
'entropy': Clipperz.PM.Crypto.passwordEntropy(passwordString)
});
},
refreshEntropyValue: function () {
this.setState({
'entropy': Clipperz.PM.Crypto.passwordEntropy(this.state['password'])
});
},
changeStateWithTargetValue: function (aKey, shouldUpdatePasswordValue) {
var self = this;
return function (anEvent) {
var newState = {};
newState[aKey] = anEvent.target.value;
self.setState(newState);
if (shouldUpdatePasswordValue) {
MochiKit.Async.callLater(0.1, self.refreshPasswordValue);
} else {
MochiKit.Async.callLater(0.1, self.refreshEntropyValue);
}
}
},
changeStateWithCheckbox: function (aKey) {
var self = this;
return function (anEvent) {
var newState = {};
newState[aKey] = anEvent.target.checked;
self.setState(newState);
MochiKit.Async.callLater(0.1, self.updateCharset);
}
},
// isPasswordField: function () {
// return this.props['field']['actionType'] == 'PASSWORD';
// },
componentDidMount: function () {
this.updateCharset();
this.refreshPasswordValue();
},
render: function () {
var goodEntropy = 128;
var entropyPercentage;
var entropyWidth;
var entropyColor;
var result;
entropyPercentage = Math.min(this.state['entropy'] / goodEntropy * 100, 100);
entropyWidth = (100 - entropyPercentage)+ '%';
entropyColor = this.gradientColors[Math.floor(entropyPercentage / 10)];
result = React.DOM.div({'className':'passwordGenerator'}, [
React.DOM.div({'className':'passwordGeneratorMask', 'onClick':this.props['closeClallback']}),
React.DOM.div({'className':'passwordGeneratorBaloon'}, [
React.DOM.form({}, [
React.DOM.div({'className':'optionsWrapper'}, [
React.DOM.header({}, [
React.DOM.div({'className':'button', 'onClick':this.toggleOptions}, "options")
]),
React.DOM.div({'className':'options ' + this.state['options']}, [
React.DOM.div({'className':'length'}, [
React.DOM.span({}, "length"),
React.DOM.input({'type':'number', 'placehoder':"", 'value':this.state['length'], 'min':"1", 'max':"99", 'onChange':this.changeStateWithTargetValue('length', true), 'ref':'length'}),
]),
React.DOM.div({'className':'charList'}, [
React.DOM.span({}, "characters"),
React.DOM.div({'className':'charsetSets'}, [
React.DOM.label({}, [ React.DOM.input({'type':'checkbox', 'checked':this.state['chars_AZ'], 'onChange':this.changeStateWithCheckbox('chars_AZ')}), "A-Z"]),
React.DOM.label({}, [ React.DOM.input({'type':'checkbox', 'checked':this.state['chars_az'], 'onChange':this.changeStateWithCheckbox('chars_az')}), "a-z"]),
React.DOM.label({}, [ React.DOM.input({'type':'checkbox', 'checked':this.state['chars_09'], 'onChange':this.changeStateWithCheckbox('chars_09')}), "0-9"]),
React.DOM.label({}, [ React.DOM.input({'type':'checkbox', 'checked':this.state['chars_space'], 'onChange':this.changeStateWithCheckbox('chars_space')}), "space"]),
React.DOM.label({}, [ React.DOM.input({'type':'checkbox', 'checked':this.state['chars_other'], 'onChange':this.changeStateWithCheckbox('chars_other')}), "!#?"]),
]),
Clipperz.PM.UI.Components.Cards.TextArea({'rows':'1', 'value':this.state['charset'], 'onChange':this.changeStateWithTargetValue('charset', true)})
]),
])
]),
React.DOM.div({'className':'passwordValue'}, [
React.DOM.div({'className':'passwordWrapper'}, [
Clipperz.PM.UI.Components.Cards.TextArea({'rows':'1', 'value':this.state['password'], 'onChange':this.changeStateWithTargetValue('password', false)}),
React.DOM.div({'className':'entropyWrapper', 'style':{'backgroundColor':entropyColor}}, [ React.DOM.div({'className':'entropy', 'style':{'width': entropyWidth}})])
]),
React.DOM.div({'className':'button', 'onClick':this.refreshPasswordValue}, "generate new password"),
]),
React.DOM.div({'className':'button setValue', 'onClick':this.setPasswordValue})
])
])
]);
return result;
},
//=========================================================================
});
Clipperz.PM.UI.Components.Cards.PasswordGenerator = React.createFactory(Clipperz.PM.UI.Components.Cards.PasswordGeneratorClass);