mirror of
http://git.whoc.org.uk/git/password-manager.git
synced 2025-10-24 01:07:35 +02:00
Implemented PIN and updated README
This commit is contained in:
@@ -39,18 +39,18 @@ MochiKit.Base.update(Clipperz.PM.PIN, {
|
||||
return this.__repr__();
|
||||
},
|
||||
|
||||
'CREDENTIALS': 'CLIPPERZ.CREDENTIALS',
|
||||
'FAILURE_COUNT': 'CLIPPERZ.FAILED_LOGIN_COUNT',
|
||||
'ENCRYPTED_PASSPHRASE_LENGTH': 1024,
|
||||
'DEFAULT_PIN_LENGTH': 5,
|
||||
'ALLOWED_RETRY': 3,
|
||||
|
||||
'LS_USERNAME': 'clipperz.pin.username',
|
||||
'LS_PASSPHRASE': 'clipperz.pin.passphrase',
|
||||
'LS_FAILURE_COUNT': 'clipperz.pin.failureCount',
|
||||
|
||||
//-------------------------------------------------------------------------
|
||||
|
||||
'isSet': function () {
|
||||
return (this.storedCredentials() != null);
|
||||
},
|
||||
|
||||
'storedCredentials': function () {
|
||||
return localStorage[this.CREDENTIALS];
|
||||
return (localStorage[this.LS_USERNAME] && localStorage[this.LS_PASSPHRASE]);
|
||||
},
|
||||
|
||||
//-------------------------------------------------------------------------
|
||||
@@ -59,7 +59,7 @@ MochiKit.Base.update(Clipperz.PM.PIN, {
|
||||
var failureCount;
|
||||
var result;
|
||||
|
||||
failureCount = localStorage[this.FAILURE_COUNT];
|
||||
failureCount = localStorage[this.LS_FAILURE_COUNT];
|
||||
|
||||
if (failureCount == null) {
|
||||
failureCount = 0
|
||||
@@ -68,10 +68,10 @@ MochiKit.Base.update(Clipperz.PM.PIN, {
|
||||
failureCount ++;
|
||||
|
||||
if (failureCount < this.ALLOWED_RETRY) {
|
||||
localStorage[this.FAILURE_COUNT] = failureCount;
|
||||
localStorage[this.LS_FAILURE_COUNT] = failureCount;
|
||||
result = failureCount;
|
||||
} else {
|
||||
this.removeLocalCredentials();
|
||||
this.disablePin();
|
||||
result = -1;
|
||||
}
|
||||
|
||||
@@ -79,11 +79,11 @@ MochiKit.Base.update(Clipperz.PM.PIN, {
|
||||
},
|
||||
|
||||
'resetFailedAttemptCount': function () {
|
||||
localStorage.removeItem(this.FAILURE_COUNT);
|
||||
localStorage.removeItem(this.LS_FAILURE_COUNT);
|
||||
},
|
||||
|
||||
'failureCount': function () {
|
||||
return localStorage[this.FAILURE_COUNT];
|
||||
return localStorage[this.LS_FAILURE_COUNT];
|
||||
},
|
||||
|
||||
//-------------------------------------------------------------------------
|
||||
@@ -92,37 +92,51 @@ MochiKit.Base.update(Clipperz.PM.PIN, {
|
||||
return Clipperz.Crypto.SHA.sha256(new Clipperz.ByteArray(aPIN));
|
||||
},
|
||||
|
||||
'credentialsWithPIN': function (aPIN) {
|
||||
var byteArrayValue;
|
||||
var decryptedValue;
|
||||
var result;
|
||||
|
||||
byteArrayValue = (new Clipperz.ByteArray()).appendBase64String(localStorage[this.CREDENTIALS]);
|
||||
decryptedValue = Clipperz.Crypto.AES.decrypt(this.deriveKeyFromPin(aPIN), byteArrayValue).asString();
|
||||
try {
|
||||
result = Clipperz.Base.evalJSON(decryptedValue);
|
||||
} catch (error) {
|
||||
result = {'username':'fakeusername', 'passphrase':'fakepassphrase'};
|
||||
'credentialsWithPIN': function(aPIN) {
|
||||
return {
|
||||
'username': localStorage[this.LS_USERNAME],
|
||||
'passphrase': this.decryptPassphraseWithPin(aPIN, localStorage[this.LS_PASSPHRASE]),
|
||||
}
|
||||
|
||||
return result;
|
||||
},
|
||||
|
||||
'setCredentialsWithPIN': function (aPIN, someCredentials) {
|
||||
var encodedValue;
|
||||
var byteArrayValue;
|
||||
var encryptedValue;
|
||||
'encryptPassphraseWithPin': function(aPIN, aPassphrase) {
|
||||
var byteArrayPassphrase = new Clipperz.ByteArray(aPassphrase);
|
||||
// var hashedPassphrase = Clipperz.Crypto.SHA.sha_d256(ba) // ??? why would i hash the passphrase???
|
||||
var randomBytesLength = this.ENCRYPTED_PASSPHRASE_LENGTH-byteArrayPassphrase.length()-1;
|
||||
var randomBytes = Clipperz.Crypto.PRNG.defaultRandomGenerator().getRandomBytes(randomBytesLength);
|
||||
var derivedKey = this.deriveKeyFromPin(aPIN);
|
||||
|
||||
encodedValue = Clipperz.Base.serializeJSON(someCredentials);
|
||||
byteArrayValue = new Clipperz.ByteArray(encodedValue);
|
||||
encryptedValue = Clipperz.Crypto.AES.encrypt(this.deriveKeyFromPin(aPIN), byteArrayValue).toBase64String();
|
||||
byteArrayPassphrase.appendByte(0);
|
||||
byteArrayPassphrase.appendBytes(randomBytes.arrayValues());
|
||||
|
||||
localStorage[this.CREDENTIALS] = encryptedValue;
|
||||
return Clipperz.Crypto.AES.encrypt(derivedKey, byteArrayPassphrase).toBase64String();
|
||||
},
|
||||
|
||||
'removeLocalCredentials': function () {
|
||||
localStorage.removeItem(this.CREDENTIALS);
|
||||
localStorage.removeItem(this.FAILURE_COUNT);
|
||||
'decryptPassphraseWithPin': function(aPIN, anEncryptedPassphrase) {
|
||||
var byteArrayEncryptedPassphrase = (new Clipperz.ByteArray()).appendBase64String(anEncryptedPassphrase);
|
||||
var derivedKey = this.deriveKeyFromPin(aPIN);
|
||||
var byteArrayPassphrase = Clipperz.Crypto.AES.decrypt(derivedKey, byteArrayEncryptedPassphrase);
|
||||
var arrayPassphrase = byteArrayPassphrase.arrayValues();
|
||||
var slicedArrayPassphrase = arrayPassphrase.slice(0, arrayPassphrase.indexOf(0));
|
||||
|
||||
return new Clipperz.ByteArray(slicedArrayPassphrase).asString();
|
||||
},
|
||||
|
||||
'updatePin': function(aUser, aPIN) {
|
||||
return Clipperz.Async.callbacks("Clipperz.PM.PIN", [
|
||||
MochiKit.Base.method(aUser, 'username'),
|
||||
MochiKit.Base.method(localStorage, 'setItem', this.LS_USERNAME),
|
||||
MochiKit.Base.method(aUser, 'getPassphrase'),
|
||||
MochiKit.Base.method(this, 'encryptPassphraseWithPin', aPIN),
|
||||
MochiKit.Base.method(localStorage, 'setItem', this.LS_PASSPHRASE),
|
||||
MochiKit.Base.method(localStorage, 'setItem', this.LS_FAILURE_COUNT, 0),
|
||||
], {trace:false});
|
||||
},
|
||||
|
||||
'disablePin': function () {
|
||||
localStorage.removeItem(this.LS_USERNAME);
|
||||
localStorage.removeItem(this.LS_PASSPHRASE);
|
||||
localStorage.removeItem(this.LS_FAILURE_COUNT);
|
||||
},
|
||||
|
||||
'isLocalStorageSupported': function() {
|
||||
|
||||
@@ -403,7 +403,7 @@ Clipperz.Base.extend(Clipperz.PM.Proxy.Offline.DataStore, Object, {
|
||||
);
|
||||
result['M2'] = M2;
|
||||
result['accountInfo'] = aConnection['userData']['accountInfo'];
|
||||
result['lock'] = '<<LOCK>>';
|
||||
result['lock'] = (aConnection['userData']['lock']) ? aConnection['userData']['lock'] : '<<LOCK>>';
|
||||
} else {
|
||||
throw new Error("Client checksum verification failed! Expected <" + M1 + ">, received <" + someParameters.parameters.M1 + ">.", "Error");
|
||||
}
|
||||
|
||||
@@ -31,15 +31,180 @@ Clipperz.PM.UI.Components.ExtraFeatures.DevicePINClass = React.createClass({
|
||||
// 'level': React.PropTypes.oneOf(['hide', 'info', 'warning', 'error']).isRequired
|
||||
},
|
||||
|
||||
getInitialState: function() {
|
||||
return {
|
||||
isEditing: false,
|
||||
pinValue: ''
|
||||
}
|
||||
},
|
||||
|
||||
_editModeLocked: false,
|
||||
|
||||
//=========================================================================
|
||||
|
||||
enterEditMode: function() {
|
||||
this.setState({
|
||||
'isEditing': true,
|
||||
'pinValue': ''
|
||||
});
|
||||
},
|
||||
|
||||
exitEditMode: function() {
|
||||
this.setState({
|
||||
'isEditing': false,
|
||||
});
|
||||
},
|
||||
|
||||
lockEditMode: function() {
|
||||
this._editModeLocked = true;
|
||||
},
|
||||
|
||||
unlockEditMode: function() {
|
||||
this._editModeLocked = false;
|
||||
},
|
||||
|
||||
handleFocus: function(anEvent) {
|
||||
anEvent.preventDefault();
|
||||
|
||||
this.refs['pinValue'].getDOMNode().focus();
|
||||
},
|
||||
|
||||
handleBlur: function(anEvent) {
|
||||
if (! this._editModeLocked) {
|
||||
if (anEvent.target.value.length < this.props['PIN'].DEFAULT_PIN_LENGTH) {
|
||||
this.exitEditMode();
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
handleKeyDown: function(anEvent) {
|
||||
if (anEvent.keyCode == 27) {
|
||||
this.refs['pinValue'].getDOMNode().blur();
|
||||
}
|
||||
},
|
||||
|
||||
handleChange: function(anEvent) {
|
||||
if (anEvent.target.value.length == this.props['PIN'].DEFAULT_PIN_LENGTH) {
|
||||
MochiKit.Signal.signal(Clipperz.Signal.NotificationCenter, 'updatePIN', anEvent.target.value);
|
||||
this.refs['pinValue'].getDOMNode().blur();
|
||||
this.exitEditMode();
|
||||
} else {
|
||||
this.setState({
|
||||
'pinValue': anEvent.target.value
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
handleCheckboxChange: function(anEvent) {
|
||||
if (this.props['PIN'].isSet() || this.state['isEditing']) {
|
||||
MochiKit.Signal.signal(Clipperz.Signal.NotificationCenter, 'disablePIN', anEvent.target.value);
|
||||
this.exitEditMode();
|
||||
} else {
|
||||
this.enterEditMode();
|
||||
}
|
||||
},
|
||||
|
||||
handleResetPIN: function() {
|
||||
this.enterEditMode();
|
||||
},
|
||||
|
||||
//=========================================================================
|
||||
|
||||
// renderDigitInputs: function() {
|
||||
// var i;
|
||||
// var result;
|
||||
|
||||
// result = [];
|
||||
// for (i = 0; i<this.props['PIN'].DEFAULT_PIN_LENGTH; i++) {
|
||||
// var boxIsFull = (this.state['isEditing']&&this.state['pinValue'][i])
|
||||
// ||
|
||||
// (!this.state['isEditing']&&this.props['PIN'].isSet())
|
||||
|
||||
// result.push(React.DOM.input({
|
||||
// 'key': 'pin-digit-'+i,
|
||||
// 'ref': 'pin-digit-'+i,
|
||||
// 'name': 'pin-digit-'+i,
|
||||
// 'className': 'pinDigit',
|
||||
// 'readOnly': true,
|
||||
// 'type': 'text',
|
||||
// 'value': boxIsFull ? '*' : '',
|
||||
// 'min': 0,
|
||||
// 'max': 9,
|
||||
// 'disabled': !this.state['isEditing'],
|
||||
// 'onFocus': this.handleFocus,
|
||||
// }));
|
||||
// }
|
||||
|
||||
// return result;
|
||||
// },
|
||||
|
||||
//-------------------------------------------------------------------------
|
||||
|
||||
componentDidUpdate: function() {
|
||||
if (this.state['isEditing']) {
|
||||
this.refs['pinValue'].getDOMNode().focus();
|
||||
}
|
||||
},
|
||||
|
||||
render: function () {
|
||||
return React.DOM.div({className:'extraFeature devicePIN'}, [
|
||||
var displayedPin;
|
||||
var isFormEnabled = (this.props['PIN'].isSet() || this.state.isEditing);
|
||||
var isResetButtonEnabled = (! this.state['isEditing'] && this.props['PIN'].isSet());
|
||||
|
||||
if (this.state.isEditing) {
|
||||
displayedPin = this.state['pinValue'];
|
||||
} else {
|
||||
displayedPin = (this.props['PIN'].isSet()) ? '*****' : '';
|
||||
}
|
||||
|
||||
return React.DOM.div({className:'extraFeature devicePIN'}, [
|
||||
React.DOM.div({'className':'header'}, [
|
||||
React.DOM.h1({}, "Device PIN"),
|
||||
React.DOM.div({'className':'description'}, [
|
||||
React.DOM.p({}, "You may create a 5-digit PIN to be used instead of your passphrase. Please note that the PIN is specific to the device you are now using."),
|
||||
React.DOM.p({}, [
|
||||
React.DOM.strong({}, "Warning"),
|
||||
": enabling a PIN on your device may represent a security risk! Make sure to keep the device with you at all times!",
|
||||
]),
|
||||
]),
|
||||
]),
|
||||
React.DOM.div({'className': 'content'}, [
|
||||
React.DOM.h3({}, this.props['PIN'])
|
||||
React.DOM.form({},[
|
||||
React.DOM.p({}, [
|
||||
React.DOM.input({
|
||||
'type': 'checkbox',
|
||||
'key': 'pinEnabled',
|
||||
'checked': isFormEnabled,
|
||||
'onChange': this.handleCheckboxChange,
|
||||
'onMouseDown': this.lockEditMode,
|
||||
'onMouseUp': this.unlockEditMode,
|
||||
}),
|
||||
React.DOM.label({
|
||||
'key': 'pinEnabledLabel',
|
||||
'htmlFor': 'pinEnabled',
|
||||
'onClick': this.handleCheckboxChange,
|
||||
'onMouseDown': this.lockEditMode,
|
||||
'onMouseUp': this.unlockEditMode,
|
||||
}, "Enable PIN on your device")
|
||||
]),
|
||||
// this.renderDigitInputs(),
|
||||
React.DOM.input({
|
||||
'type': 'tel',
|
||||
'key': 'pinValue',
|
||||
'ref': 'pinValue',
|
||||
'className': 'pinValue',
|
||||
'disabled': !this.state['isEditing'],
|
||||
'onKeyDown': this.handleKeyDown,
|
||||
'onChange': this.handleChange,
|
||||
'onBlur': this.handleBlur,
|
||||
'value': displayedPin,
|
||||
// 'style': {'position': 'fixed', 'top': -1000}
|
||||
}),
|
||||
React.DOM.a({
|
||||
'className': 'button'+(isResetButtonEnabled ? '' : ' disabled'),
|
||||
'onClick': (isResetButtonEnabled) ? this.handleResetPIN : null
|
||||
}, "Reset PIN"),
|
||||
])
|
||||
])
|
||||
]);
|
||||
},
|
||||
|
||||
@@ -71,7 +71,7 @@ Clipperz.PM.UI.Components.ExtraFeatures.PreferencesClass = React.createClass({
|
||||
return MochiKit.Base.bind(function (anEvent) {
|
||||
var value = anEvent.target.value;
|
||||
|
||||
console.log("HANDLE KEY DOWN", anEvent, anEvent.keyCode, value);
|
||||
// console.log("HANDLE KEY DOWN", anEvent, anEvent.keyCode, value);
|
||||
if (anEvent.target.defaultValue != value) {
|
||||
switch (anEvent.keyCode) {
|
||||
case 9: // tab
|
||||
@@ -80,7 +80,7 @@ console.log("HANDLE KEY DOWN", anEvent, anEvent.keyCode, value);
|
||||
anEvent.target.defaultValue = anEvent.target.value;
|
||||
break;
|
||||
case 27: // escape
|
||||
console.log("ESCAPE");
|
||||
// console.log("ESCAPE");
|
||||
anEvent.target.value = anEvent.target.defaultValue;
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -45,12 +45,18 @@ Clipperz.PM.UI.Components.Pages.LoginPageClass = React.createClass({
|
||||
return {
|
||||
username: '',
|
||||
passphrase: '',
|
||||
pin: ''
|
||||
pin: '',
|
||||
};
|
||||
},
|
||||
|
||||
//=========================================================================
|
||||
|
||||
mode: function() {
|
||||
return (this.props['mode'] == 'CREDENTIALS' || this.props['forceCredentials']) ? 'CREDENTIALS' : 'PIN';
|
||||
},
|
||||
|
||||
//=========================================================================
|
||||
|
||||
handleChange: function (anEvent) {
|
||||
var refs = this.refs;
|
||||
var refName = MochiKit.Base.filter(function (aRefName) { return refs[aRefName].getDOMNode() == anEvent.target}, MochiKit.Base.keys(this.refs))[0];
|
||||
@@ -61,7 +67,7 @@ Clipperz.PM.UI.Components.Pages.LoginPageClass = React.createClass({
|
||||
},
|
||||
|
||||
pollForChanges: function() {
|
||||
if (this.props.mode == 'CREDENTIALS') {
|
||||
if (this.mode() == 'CREDENTIALS') {
|
||||
var newState;
|
||||
|
||||
var usernameValue = this.refs['username'].getDOMNode().value;
|
||||
@@ -102,8 +108,8 @@ Clipperz.PM.UI.Components.Pages.LoginPageClass = React.createClass({
|
||||
|
||||
return (
|
||||
((this.state['username'] != '') && (this.state['passphrase'] != ''))
|
||||
||
|
||||
(this.state['pin'] != '')
|
||||
// ||
|
||||
// (this.state['pin'] != '')
|
||||
)
|
||||
&&
|
||||
!this.props['disabled'];
|
||||
@@ -121,9 +127,7 @@ Clipperz.PM.UI.Components.Pages.LoginPageClass = React.createClass({
|
||||
]);
|
||||
},
|
||||
|
||||
handlePINSubmit: function (event) {
|
||||
event.preventDefault();
|
||||
|
||||
submitPIN: function (event) {
|
||||
this.refs['pin'].getDOMNode().blur();
|
||||
|
||||
var credentials = {
|
||||
@@ -133,19 +137,82 @@ Clipperz.PM.UI.Components.Pages.LoginPageClass = React.createClass({
|
||||
MochiKit.Signal.signal(Clipperz.Signal.NotificationCenter, 'doLogin', credentials);
|
||||
},
|
||||
|
||||
forcePassphraseLogin: function() {
|
||||
MochiKit.Signal.signal(Clipperz.Signal.NotificationCenter, 'forcePassphraseLogin');
|
||||
},
|
||||
|
||||
handlePinChange: function(anEvent) {
|
||||
if (anEvent.target.value.length == Clipperz.PM.PIN.DEFAULT_PIN_LENGTH) {
|
||||
this.submitPIN();
|
||||
}
|
||||
|
||||
this.setState({
|
||||
'pin': anEvent.target.value
|
||||
})
|
||||
},
|
||||
|
||||
// handlePinFocus: function(anEvent) {
|
||||
// // anEvent.preventDefault();
|
||||
// this.refs['pin'].getDOMNode().focus();
|
||||
// },
|
||||
|
||||
// pinFormDigits: function() {
|
||||
// var i;
|
||||
// var result;
|
||||
|
||||
// result = [];
|
||||
// for (i = 0; i<Clipperz.PM.PIN.DEFAULT_PIN_LENGTH; i++) {
|
||||
// result.push(React.DOM.input({
|
||||
// 'key': 'pin-digit-'+i,
|
||||
// 'ref': 'pin-digit-'+i,
|
||||
// 'name': 'pin-digit-'+i,
|
||||
// 'className': 'pinDigit',
|
||||
// 'readOnly': true,
|
||||
// 'type': 'text',
|
||||
// 'value': this.state['pin'][i],
|
||||
// 'onFocus': this.handlePinFocus,
|
||||
// }));
|
||||
// }
|
||||
|
||||
// return result;
|
||||
// },
|
||||
|
||||
pinForm: function () {
|
||||
return React.DOM.form({'className':'pinForm pin', 'autoComplete':'off', 'onChange':this.handleChange, 'onSubmit':this.handlePINSubmit}, [
|
||||
React.DOM.div({'key':'pinFormDiv'},[
|
||||
React.DOM.label({'for':'pin'}, "pin"),
|
||||
React.DOM.input({'type':'text', 'name':'pin', 'ref':'pin', placeholder:"PIN", 'key':'pin', 'autocapitalize':'none'})
|
||||
]),
|
||||
React.DOM.button({'key':'submitButton', 'type':'submit', 'disabled':this.props.disabled, 'className':'button'}, "login")
|
||||
]);
|
||||
return React.DOM.form({
|
||||
'className':'pinForm pin',
|
||||
'autoComplete':'off',
|
||||
'onSubmit': function(anEvent) {anEvent.preventDefault();},
|
||||
}, [
|
||||
React.DOM.div({'key':'pinFormDiv'},[
|
||||
React.DOM.label({'htmlFor':'pin'}, "Enter your PIN"),
|
||||
React.DOM.input({
|
||||
'type':'tel',
|
||||
'name':'pin',
|
||||
'ref':'pin',
|
||||
'id': 'pinValue',
|
||||
'className': 'pinValue',
|
||||
'placeholder':"PIN",
|
||||
'key':'pin',
|
||||
'autoCapitalize':'none',
|
||||
'value': this.state['pin'],
|
||||
'onChange': this.handlePinChange,
|
||||
}),
|
||||
// React.DOM.div({'className': 'pinContainer'}, this.pinFormDigits()),
|
||||
React.DOM.a({
|
||||
'className': 'passphraseLogin',
|
||||
'onClick': this.forcePassphraseLogin,
|
||||
}, "Login with passphrase")
|
||||
]),
|
||||
// React.DOM.button({'key':'submitButton', 'type':'submit', 'disabled':this.props.disabled, 'className':'button'}, "login")
|
||||
]);
|
||||
},
|
||||
|
||||
setInitialFocus: function () {
|
||||
if (this.props.mode == 'PIN') {
|
||||
this.refs['pin'].getDOMNode().select();
|
||||
if (this.mode() == 'PIN') {
|
||||
this.setState({
|
||||
'pin': ''
|
||||
})
|
||||
this.refs['pin'].getDOMNode().focus();
|
||||
} else {
|
||||
if (this.refs['username'].getDOMNode().value == '') {
|
||||
this.refs['username'].getDOMNode().focus();
|
||||
@@ -162,7 +229,6 @@ Clipperz.PM.UI.Components.Pages.LoginPageClass = React.createClass({
|
||||
},
|
||||
|
||||
render: function() {
|
||||
//console.log("LOGIN PAGE", this.props);
|
||||
// var registrationLink = React.DOM.footer({'key':'registrationLink', 'className':'registrationLink'}, [
|
||||
// React.DOM.a({'key':'signup', 'onClick':this.handleRegistrationLinkClick}, "Sign up")
|
||||
// ]);
|
||||
@@ -183,7 +249,7 @@ Clipperz.PM.UI.Components.Pages.LoginPageClass = React.createClass({
|
||||
]),
|
||||
React.DOM.div({'key':'formWrapper', 'className':'form body'}, [
|
||||
React.DOM.div({'className':'bodyContent'}, [
|
||||
this.props['mode'] == 'PIN' ? this.pinForm() : this.loginForm(),
|
||||
this.mode() == 'PIN' ? this.pinForm() : this.loginForm(),
|
||||
]),
|
||||
]),
|
||||
this.props['isNewUserRegistrationAvailable'] ? registrationLink : null,
|
||||
|
||||
@@ -41,6 +41,12 @@ Clipperz.PM.UI.Components.Pages.UnlockPageClass = React.createClass({
|
||||
|
||||
//=========================================================================
|
||||
|
||||
mode: function() {
|
||||
return (this.props['mode'] == 'CREDENTIALS' || this.props['forceCredentials']) ? 'CREDENTIALS' : 'PIN';
|
||||
},
|
||||
|
||||
//=========================================================================
|
||||
|
||||
handleChange: function (anEvent) {
|
||||
var newState = {};
|
||||
|
||||
@@ -49,27 +55,58 @@ Clipperz.PM.UI.Components.Pages.UnlockPageClass = React.createClass({
|
||||
this.setState(newState);
|
||||
},
|
||||
|
||||
handlePinChange: function(anEvent) {
|
||||
if (anEvent.target.value.length == this.props['PIN'].DEFAULT_PIN_LENGTH) {
|
||||
this.submitPIN();
|
||||
}
|
||||
|
||||
this.setState({
|
||||
'pin': anEvent.target.value
|
||||
})
|
||||
},
|
||||
|
||||
//=========================================================================
|
||||
|
||||
handlePassphraseSubmit: function (event) {
|
||||
event.preventDefault();
|
||||
|
||||
this.refs['passphrase'].getDOMNode().blur();
|
||||
MochiKit.Signal.signal(Clipperz.Signal.NotificationCenter, 'unlock', this.refs['passphrase'].getDOMNode().value);
|
||||
MochiKit.Signal.signal(Clipperz.Signal.NotificationCenter, 'unlock', this.refs['passphrase'].getDOMNode().value, 'PASSPHRASE');
|
||||
|
||||
this.resetUnlockForm();
|
||||
},
|
||||
|
||||
submitPIN: function() {
|
||||
this.refs['pin'].getDOMNode().blur();
|
||||
|
||||
var pin = this.refs['pin'].getDOMNode().value;
|
||||
|
||||
MochiKit.Signal.signal(Clipperz.Signal.NotificationCenter, 'unlock', pin, 'PIN');
|
||||
|
||||
this.resetUnlockForm();
|
||||
},
|
||||
|
||||
resetUnlockForm: function() {
|
||||
this.refs['passphrase'].getDOMNode().value = '';
|
||||
this.replaceState(this.getInitialState());
|
||||
if (this.mode() == 'CREDENTIALS') {
|
||||
this.refs['passphrase'].getDOMNode().value = '';
|
||||
this.refs['passphrase'].getDOMNode().blur();
|
||||
} else if (this.mode() == 'PIN') {
|
||||
this.refs['pin'].getDOMNode().value = '';
|
||||
this.refs['pin'].getDOMNode().blur();
|
||||
}
|
||||
},
|
||||
|
||||
forcePassphraseUnlock: function() {
|
||||
MochiKit.Signal.signal(Clipperz.Signal.NotificationCenter, 'forcePassphraseUnlock');
|
||||
},
|
||||
|
||||
//-------------------------------------------------------------------------
|
||||
|
||||
setInitialFocus: function () {
|
||||
if (this.props.mode == 'PIN') {
|
||||
this.refs['pin'].getDOMNode().select();
|
||||
if (this.mode() == 'PIN') {
|
||||
this.refs['pin'].getDOMNode().focus();
|
||||
} else {
|
||||
this.refs['passphrase'].getDOMNode().select();
|
||||
this.refs['passphrase'].getDOMNode().focus();
|
||||
}
|
||||
},
|
||||
|
||||
@@ -91,6 +128,35 @@ Clipperz.PM.UI.Components.Pages.UnlockPageClass = React.createClass({
|
||||
]);
|
||||
},
|
||||
|
||||
pinForm: function () {
|
||||
return React.DOM.form({
|
||||
'className':'pinForm pin',
|
||||
'autoComplete':'off',
|
||||
}, [
|
||||
React.DOM.div({'key':'pinFormDiv'},[
|
||||
React.DOM.label({'htmlFor':'pin'}, "Enter your PIN"),
|
||||
React.DOM.input({
|
||||
'type':'tel',
|
||||
'name':'pin',
|
||||
'ref':'pin',
|
||||
'id': 'pinValue',
|
||||
'className': 'pinValue',
|
||||
'placeholder':"PIN",
|
||||
'key':'pin',
|
||||
'autoCapitalize':'none',
|
||||
'value': this.state['pin'],
|
||||
'onChange': this.handlePinChange,
|
||||
}),
|
||||
// React.DOM.div({'className': 'pinContainer'}, this.pinFormDigits()),
|
||||
React.DOM.a({
|
||||
'className': 'passphraseLogin',
|
||||
'onClick': this.forcePassphraseUnlock,
|
||||
}, "Unlock with passphrase")
|
||||
]),
|
||||
// React.DOM.button({'key':'submitButton', 'type':'submit', 'disabled':this.props.disabled, 'className':'button'}, "login")
|
||||
]);
|
||||
},
|
||||
|
||||
shouldEnableUnlockButton: function () {
|
||||
var result;
|
||||
|
||||
@@ -103,6 +169,10 @@ Clipperz.PM.UI.Components.Pages.UnlockPageClass = React.createClass({
|
||||
!this.props['disabled'];
|
||||
},
|
||||
|
||||
// componentDidUpdate: function() {
|
||||
// this.setInitialFocus();
|
||||
// },
|
||||
|
||||
render: function() {
|
||||
return React.DOM.div({'key':'unlockForm', 'className':'unlockForm content ' + this.props['style']}, [
|
||||
Clipperz.PM.UI.Components.AccountStatus(MochiKit.Base.update(this.props['proxyInfo'])),
|
||||
@@ -114,7 +184,7 @@ Clipperz.PM.UI.Components.Pages.UnlockPageClass = React.createClass({
|
||||
]),
|
||||
React.DOM.div({'key':'formWrapper', 'className':'form body'}, [
|
||||
React.DOM.div({'className':'bodyContent'}, [
|
||||
this.props.mode == 'PIN' ? this.pinForm() : this.loginForm(),
|
||||
this.mode() == 'PIN' ? this.pinForm() : this.loginForm(),
|
||||
]),
|
||||
]),
|
||||
React.DOM.footer({'key':'footer'}, [
|
||||
|
||||
@@ -162,20 +162,14 @@ Clipperz.PM.UI.Components.Panels.ExtraFeaturesPanelClass = React.createClass({
|
||||
// React.DOM.p({}, "Manage your OTPs.")
|
||||
// ])
|
||||
]),
|
||||
/*
|
||||
|
||||
React.DOM.li({'key':'account_3', 'onClick':this.toggleExtraFeatureComponent('DevicePIN')}, [
|
||||
React.DOM.h2({}, "Device PIN"),
|
||||
React.DOM.div({}, [
|
||||
React.DOM.p({}, "Configure a PIN that will allow to get access to your cards, but only on this device.")
|
||||
])
|
||||
// React.DOM.div({}, [
|
||||
// React.DOM.p({}, "Configure a PIN that will allow to get access to your cards, but only on this device.")
|
||||
// ])
|
||||
]),
|
||||
React.DOM.li({'key':'account_4'}, [
|
||||
React.DOM.h2({}, "Preferences"),
|
||||
React.DOM.div({}, [
|
||||
React.DOM.p({}, "")
|
||||
])
|
||||
]),
|
||||
*/
|
||||
|
||||
React.DOM.li({'key':'account_5', 'onClick':this.toggleExtraFeatureComponent('DeleteAccount'), 'className':(this.state['extraFeatureComponentName'] == 'DeleteAccount') ? 'selected' : ''}, [
|
||||
React.DOM.h2({}, "Delete account"),
|
||||
// React.DOM.div({}, [
|
||||
|
||||
@@ -293,7 +293,7 @@ MochiKit.Base.update(Clipperz.PM.UI.ImportContext.prototype, {
|
||||
if (isUploadingFile) {
|
||||
var isExportContent;
|
||||
|
||||
isExportContent = new RegExp('.*<textarea>(.*)<\/textarea>.*', 'g');
|
||||
isExportContent = new RegExp('[\\s\\S]*<textarea>([\\s\\S]*)<\/textarea>[\\s\\S]*', 'g');
|
||||
if (isExportContent.test(aValue)) {
|
||||
textarea = MochiKit.DOM.TEXTAREA();
|
||||
textarea.innerHTML = aValue.replace(isExportContent, '$1');
|
||||
|
||||
@@ -68,6 +68,7 @@ Clipperz.PM.UI.MainController = function() {
|
||||
'doLogin', 'registerNewUser', 'showRegistrationForm', 'goBack',
|
||||
'logout',
|
||||
'enableLock', 'disableLock', 'unlock',
|
||||
'updatePIN', 'disablePIN', 'forcePassphraseLogin', 'forcePassphraseUnlock',
|
||||
'changePassphrase', 'deleteAccount',
|
||||
/*'updateUserPreferences',*/ 'setPreference',
|
||||
'updateOTPListAndDetails', 'createNewOTP', 'deleteOTPs', 'changeOTPLabel',
|
||||
@@ -328,6 +329,16 @@ Clipperz.log("THE BROWSER IS OFFLINE");
|
||||
MochiKit.Async.callLater(0.5, MochiKit.Base.method(registrationPage, 'setInitialFocus'));
|
||||
},
|
||||
|
||||
forcePassphraseLogin_handler: function() {
|
||||
this.pages()['loginPage'].setProps({'forceCredentials': true});
|
||||
MochiKit.Async.callLater(0.1, MochiKit.Base.method(this.pages()['loginPage'], 'setInitialFocus'));
|
||||
},
|
||||
|
||||
forcePassphraseUnlock_handler: function() {
|
||||
this.pages()['unlockPage'].setProps({'forceCredentials': true});
|
||||
MochiKit.Async.callLater(0.1, MochiKit.Base.method(this.pages()['unlockPage'], 'setInitialFocus'));
|
||||
},
|
||||
|
||||
//=========================================================================
|
||||
|
||||
doLogin_handler: function (event) {
|
||||
@@ -411,35 +422,57 @@ Clipperz.log("THE BROWSER IS OFFLINE");
|
||||
return deferredResult;
|
||||
},
|
||||
|
||||
unlock_handler: function(aPassphrase) {
|
||||
unlock_handler: function(aCredential, aCredentialType) {
|
||||
var deferredResult;
|
||||
var passphrase;
|
||||
|
||||
var user = this.user();
|
||||
var unlockPage = this.pages()['unlockPage'];
|
||||
var overlay = this.overlay();
|
||||
|
||||
|
||||
passphrase = (aCredentialType=='PIN') ? Clipperz.PM.PIN.credentialsWithPIN(aCredential)['passphrase'] : aCredential;
|
||||
|
||||
overlay.show("validating…");
|
||||
deferredResult = new Clipperz.Async.Deferred('MainController.unlock_handler', {trace:false});
|
||||
deferredResult.addMethod(unlockPage, 'setProps', {'disabled': true});
|
||||
|
||||
deferredResult.addMethod(user, 'unlock', function() { return MochiKit.Async.succeed(aPassphrase); });
|
||||
deferredResult.addErrback(function (aValue) {
|
||||
deferredResult.addMethod(user, 'unlock', function() { return MochiKit.Async.succeed(passphrase); });
|
||||
deferredResult.addErrback(MochiKit.Base.bind(function (aValue) {
|
||||
var innerDeferredResult;
|
||||
var errorMessage;
|
||||
|
||||
errorMessage = 'failed';
|
||||
if (aCredentialType=='PIN') {
|
||||
var attemptsLeft = Clipperz.PM.PIN.recordFailedAttempt();
|
||||
|
||||
if (attemptsLeft == -1) {
|
||||
errorMessage = 'PIN resetted';
|
||||
}
|
||||
}
|
||||
|
||||
innerDeferredResult = new Clipperz.Async.Deferred('MainController.unlock_handler <incorrect passphrase>', {trace:false});
|
||||
innerDeferredResult.addMethod(unlockPage, 'setProps', {'disabled': false});
|
||||
innerDeferredResult.addMethod(unlockPage, 'setProps', {
|
||||
'disabled': false,
|
||||
'mode': this.loginMode(),
|
||||
});
|
||||
innerDeferredResult.addMethod(unlockPage, 'setInitialFocus');
|
||||
innerDeferredResult.addMethod(overlay, 'failed', "", 1);
|
||||
innerDeferredResult.addMethod(overlay, 'failed', errorMessage, 1);
|
||||
innerDeferredResult.addCallback(MochiKit.Async.fail, aValue);
|
||||
innerDeferredResult.callback();
|
||||
|
||||
return aValue;
|
||||
});
|
||||
}, this));
|
||||
|
||||
if (aCredentialType=='PIN') {
|
||||
deferredResult.addMethod(Clipperz.PM.PIN, 'resetFailedAttemptCount');
|
||||
}
|
||||
deferredResult.addMethod(this, 'updateUserPreferences');
|
||||
deferredResult.addMethod(this, 'moveInPage', this.currentPage(), 'mainPage');
|
||||
deferredResult.addMethod(this, 'refreshUI');
|
||||
deferredResult.addMethod(unlockPage, 'setProps', {'disabled': false});
|
||||
deferredResult.addMethod(unlockPage, 'setProps', {
|
||||
'disabled': false,
|
||||
'forceCredentials': false,
|
||||
});
|
||||
deferredResult.addMethod(unlockPage, 'resetUnlockForm');
|
||||
deferredResult.addCallback(MochiKit.Signal.signal, Clipperz.Signal.NotificationCenter, 'enableLock');
|
||||
deferredResult.addMethod(overlay, 'done', "", 0.5);
|
||||
@@ -448,7 +481,7 @@ Clipperz.log("THE BROWSER IS OFFLINE");
|
||||
|
||||
return deferredResult;
|
||||
|
||||
// this.user().setPassphraseFunction(function(){return aPassphrase;});
|
||||
// this.user().setPassphraseFunction(function(){return passphrase;});
|
||||
// TODO: check if passphrase is correct by try/catch on decrypting something
|
||||
// this.moveOutPage(this.currentPage(), 'mainPage');
|
||||
// TODO: check why the unlock form keeps the value stored (doesn't happen with the login form...)
|
||||
@@ -816,12 +849,29 @@ Clipperz.log("THE BROWSER IS OFFLINE");
|
||||
MochiKit.Signal.disconnectAll(Clipperz.Signal.NotificationCenter, 'lock');
|
||||
},
|
||||
|
||||
updatePIN_handler: function(aPIN) {
|
||||
return Clipperz.Async.callbacks("MainController.updatePIN_handler", [
|
||||
MochiKit.Base.method(this.overlay(), 'show', "updating …", true),
|
||||
MochiKit.Base.method(Clipperz.PM.PIN, 'updatePin', this.user(), aPIN),
|
||||
MochiKit.Base.method(this.overlay(), 'done', "saved", 1)
|
||||
], {trace:false});
|
||||
},
|
||||
|
||||
disablePIN_handler: function() {
|
||||
return Clipperz.Async.callbacks("MainController.disablePIN_handler", [
|
||||
MochiKit.Base.method(this.overlay(), 'show', "disabling …", true),
|
||||
MochiKit.Base.method(Clipperz.PM.PIN, 'disablePin'),
|
||||
MochiKit.Base.method(this.overlay(), 'done', "saved", 1)
|
||||
], {trace:false});
|
||||
},
|
||||
|
||||
resetLockTimeout: function () {
|
||||
if (this.user()) {
|
||||
return Clipperz.Async.callbacks("MainController.resetLockTimeout", [
|
||||
MochiKit.Base.method(this.user(), 'getPreference', 'lock'),
|
||||
MochiKit.Base.bind(function (someLockInfo) {
|
||||
if (this._lockTimeout) {
|
||||
// console.log("clearing previous lock timer");
|
||||
clearTimeout(this._lockTimeout);
|
||||
}
|
||||
|
||||
@@ -990,7 +1040,7 @@ Clipperz.log("THE BROWSER IS OFFLINE");
|
||||
errorMessage = "failure";
|
||||
} else {
|
||||
if ('pin' in anEvent) {
|
||||
errorCount = Clipperz.PM.PIN.recordFailedAttempt();
|
||||
var errorCount = Clipperz.PM.PIN.recordFailedAttempt();
|
||||
if (errorCount == -1) {
|
||||
errorMessage = "PIN resetted";
|
||||
}
|
||||
@@ -1170,7 +1220,8 @@ Clipperz.log("THE BROWSER IS OFFLINE");
|
||||
'style': this.mediaQueryStyle(),
|
||||
'isTouchDevice': this.isTouchDevice(),
|
||||
'isDesktop': this.isDesktop(),
|
||||
'hasKeyboard': this.hasKeyboard()
|
||||
'hasKeyboard': this.hasKeyboard(),
|
||||
'PIN': Clipperz.PM.PIN
|
||||
};
|
||||
},
|
||||
|
||||
@@ -1186,11 +1237,15 @@ Clipperz.log("THE BROWSER IS OFFLINE");
|
||||
|
||||
if (aPageName == 'loginPage') {
|
||||
extraProperties = {
|
||||
'mode': 'CREDENTIALS',
|
||||
'mode': this.loginMode(),
|
||||
'isNewUserRegistrationAvailable': Clipperz.PM.Proxy.defaultProxy.canRegisterNewUsers(),
|
||||
'disabled': false,
|
||||
'proxyInfo': this.proxyInfo(),
|
||||
};
|
||||
} else if (aPageName == 'unlockPage') {
|
||||
extraProperties = {
|
||||
'mode': this.loginMode(),
|
||||
}
|
||||
} else if (aPageName == 'registrationPage') {
|
||||
} else if (aPageName == 'mainPage') {
|
||||
extraProperties = {
|
||||
|
||||
Reference in New Issue
Block a user