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

Implemented PIN and updated README

This commit is contained in:
Dario Chiappetta
2015-09-30 20:09:58 +02:00
parent 4710a571e7
commit 3dcdcbbe7e
15 changed files with 588 additions and 138 deletions

View File

@@ -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() {

View File

@@ -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");
}

View File

@@ -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"),
])
])
]);
},

View File

@@ -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;
}

View File

@@ -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,

View File

@@ -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'}, [

View File

@@ -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({}, [

View File

@@ -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');

View File

@@ -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 = {