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

@ -4,32 +4,32 @@
##What does Clipperz do? ##What does Clipperz do?
Clipperz is an online vault where you can store confidential data without worrying about security. It can be used to save and manage passwords, private notes, burglar alarm codes, credit and debit card details, PINs, software keys, … Clipperz is a smart online vault where you can store confidential data without worrying about security. It can be used to save and manage passwords, private notes, burglar alarm codes, credit and debit card details, PINs, software keys, …
Since passwords are the most common type of private information that you need to protect, we have added a great deal of functionality to make Clipperz a great [online password manager][home] thus solving the “password fatigue” problem. Since passwords are the most common type of private information that you need to protect, we have added a great deal of functionality to make Clipperz a great [online password manager][home]. Read more on the [Clipperz website][home].
**Clipperz makes the Internet the most convenient and safe place to keep you most precious and sensitive data.** **Clipperz makes the Internet the most convenient and safe place to keep you most precious and sensitive data.**
Read more on the [Clipperz website][home].
[home]: https://clipperz.is [home]: https://clipperz.is
## Why an open source version of Clipperz? ## Why an open source version of Clipperz?
Because we want to enable as many people as possible to play with our code. So that they can start trusting it. The code, not its developers. Because we want to enable as many people as possible to play with the very same code that is powering [Clipperz online service][app]. The goal is building trust. Trust in the code, not in its developers!
In order to allow anyone not just to inspect the source code, but also to analyze the traffic it generates between client and server, we made available this open source version as an easy way to locally deploy the whole password manager web app on your machine. You can choose among the available backends (PHP/MySQL, Python/AppEngine, …) or [contribute][CA] your own.
So we released the frontend code under an open source license. That was not enough. In order to allow anyone not just to inspect the code running in the user browser, but also to analyze the traffic it generates between the client (the user's browser) and the Clipperz server, we also made available several backends that are easy to deploy.
You can choose among the available backends (PHP/MySQL, Python/AppEngine, …) or [contribute][CA] your own.
Whatever is your motivation for playing with Clipperz code, we would love to hear from you: [get in contact][contact]! Whatever is your motivation for playing with Clipperz code, we would love to hear from you: [get in contact][contact]!
## Security warning # Security warning
The open source version of Clipperz is suitable for **testing and educational purposes only**. Do not use it as an actual password management solution. The open source version of Clipperz is suitable for **testing and educational purposes only**.
As an example, the current PHP backend lacks several critical capabilities such as bot protection and concurrent sessions management, moreover it could be vulnerable to serious threats (SQL injections, remote code execution, ...). As an example, the current PHP backend lacks several critical capabilities such as bot protection and concurrent sessions management, moreover it could be vulnerable to serious threats (SQL injections, remote code execution, ...).
[CA]: https://clipperz.is/open_source/contributor_agreement Please note: the actual Clipperz service use a far more robust backend, but the communication protocol between backend and frontend is of course identical.
[contact]: https://clipperz.is/about/contacts
[clipperz]: https://clipperz.is [app]: https://clipperz.is/app/
[CA]: /open_source/contributor_agreement/
[contact]: /about/contacts/
## Donations ## Donations
@ -64,7 +64,7 @@ In order to build the deployable version, you need to invoke the following comma
git clone git@github.com:clipperz/password-manager.git git clone git@github.com:clipperz/password-manager.git
cd password-manager cd password-manager
./scripts/build install --backends php python --frontends beta gamma ./scripts/build install --backends php python --frontends delta
The output will be available in the `target` folder, with a separate folder for each backend (currently the available options are `php` and `python`). The output will be available in the `target` folder, with a separate folder for each backend (currently the available options are `php` and `python`).
The script, invoked with these parameters, will build both the full version (`install` -> index.html) and the debug version (index_debug.html) of the specified frontends. The script, invoked with these parameters, will build both the full version (`install` -> index.html) and the debug version (index_debug.html) of the specified frontends.
@ -87,14 +87,12 @@ The only file that needs to be `build`, and not read directly from the file syst
In order to build this file, the following command should be executed: In order to build this file, the following command should be executed:
./scripts/build --frontends beta gamma gamma.mobile --backends dev ./scripts/build --frontends delta --backends dev
Once the index.html files have been built (one for each frontend) and a backend is running and has been correctly configured in the proxy script, it is possible to access the different versions of the application at the following URLs: Once the index.html file has been built and a backend is running and has been correctly configured in the proxy script, it is possible to access the application at the following URL:
- `http://localhost:8888/beta/index.html` - `http://localhost:8888/delta/index.html`
- `http://localhost:8888/gamma/index.html`
- `http://localhost:8888/gamma/index.mobile.html`
## Installing ## Installing

View File

@ -119,7 +119,6 @@ http://jonibologna.com/flexbox-cheatsheet/
-ms-transform: rotate(0deg) translate(0, 0); -ms-transform: rotate(0deg) translate(0, 0);
-o-transform: rotate(0deg) translate(0, 0); -o-transform: rotate(0deg) translate(0, 0);
transform: rotate(0deg) translate(0, 0); } transform: rotate(0deg) translate(0, 0); }
100% { 100% {
-webkit-transform: rotate(359deg) translate(0, 0); -webkit-transform: rotate(359deg) translate(0, 0);
-moz-transform: rotate(359deg) translate(0, 0); -moz-transform: rotate(359deg) translate(0, 0);
@ -133,7 +132,6 @@ http://jonibologna.com/flexbox-cheatsheet/
-ms-transform: rotate(0deg) translate(0, 0); -ms-transform: rotate(0deg) translate(0, 0);
-o-transform: rotate(0deg) translate(0, 0); -o-transform: rotate(0deg) translate(0, 0);
transform: rotate(0deg) translate(0, 0); } transform: rotate(0deg) translate(0, 0); }
100% { 100% {
-webkit-transform: rotate(359deg) translate(0, 0); -webkit-transform: rotate(359deg) translate(0, 0);
-moz-transform: rotate(359deg) translate(0, 0); -moz-transform: rotate(359deg) translate(0, 0);
@ -147,7 +145,6 @@ http://jonibologna.com/flexbox-cheatsheet/
-ms-transform: rotate(0deg) translate(0, 0); -ms-transform: rotate(0deg) translate(0, 0);
-o-transform: rotate(0deg) translate(0, 0); -o-transform: rotate(0deg) translate(0, 0);
transform: rotate(0deg) translate(0, 0); } transform: rotate(0deg) translate(0, 0); }
100% { 100% {
-webkit-transform: rotate(359deg) translate(0, 0); -webkit-transform: rotate(359deg) translate(0, 0);
-moz-transform: rotate(359deg) translate(0, 0); -moz-transform: rotate(359deg) translate(0, 0);
@ -161,7 +158,6 @@ http://jonibologna.com/flexbox-cheatsheet/
-ms-transform: rotate(0deg) translate(0, 0); -ms-transform: rotate(0deg) translate(0, 0);
-o-transform: rotate(0deg) translate(0, 0); -o-transform: rotate(0deg) translate(0, 0);
transform: rotate(0deg) translate(0, 0); } transform: rotate(0deg) translate(0, 0); }
100% { 100% {
-webkit-transform: rotate(359deg) translate(0, 0); -webkit-transform: rotate(359deg) translate(0, 0);
-moz-transform: rotate(359deg) translate(0, 0); -moz-transform: rotate(359deg) translate(0, 0);
@ -480,73 +476,61 @@ div.overlay {
@-webkit-keyframes overlay-spin { @-webkit-keyframes overlay-spin {
from { from {
opacity: 1; } opacity: 1; }
to { to {
opacity: 0.25; } } opacity: 0.25; } }
@-moz-keyframes overlay-spin { @-moz-keyframes overlay-spin {
from { from {
opacity: 1; } opacity: 1; }
to { to {
opacity: 0.25; } } opacity: 0.25; } }
@-ms-keyframes overlay-spin { @-ms-keyframes overlay-spin {
from { from {
opacity: 1; } opacity: 1; }
to { to {
opacity: 0.25; } } opacity: 0.25; } }
@keyframes overlay-spin { @keyframes overlay-spin {
from { from {
opacity: 1; } opacity: 1; }
to { to {
opacity: 0.25; } } opacity: 0.25; } }
@-webkit-keyframes ios-overlay-show { @-webkit-keyframes ios-overlay-show {
0% { 0% {
opacity: 0; } opacity: 0; }
100% { 100% {
opacity: 1; } } opacity: 1; } }
@-moz-keyframes ios-overlay-show { @-moz-keyframes ios-overlay-show {
0% { 0% {
opacity: 0; } opacity: 0; }
100% { 100% {
opacity: 1; } } opacity: 1; } }
@-ms-keyframes ios-overlay-show { @-ms-keyframes ios-overlay-show {
0% { 0% {
opacity: 0; } opacity: 0; }
100% { 100% {
opacity: 1; } } opacity: 1; } }
@keyframes ios-overlay-show { @keyframes ios-overlay-show {
0% { 0% {
opacity: 0; } opacity: 0; }
100% { 100% {
opacity: 1; } } opacity: 1; } }
@-webkit-keyframes ios-overlay-hide { @-webkit-keyframes ios-overlay-hide {
0% { 0% {
opacity: 1; } opacity: 1; }
100% { 100% {
opacity: 0; } } opacity: 0; } }
@-moz-keyframes ios-overlay-hide { @-moz-keyframes ios-overlay-hide {
0% { 0% {
opacity: 1; } opacity: 1; }
100% { 100% {
opacity: 0; } } opacity: 0; } }
@-ms-keyframes ios-overlay-hide { @-ms-keyframes ios-overlay-hide {
0% { 0% {
opacity: 1; } opacity: 1; }
100% { 100% {
opacity: 0; } } opacity: 0; } }
@keyframes ios-overlay-hide { @keyframes ios-overlay-hide {
0% { 0% {
opacity: 1; } opacity: 1; }
100% { 100% {
opacity: 0; } } opacity: 0; } }
/* /*
@ -1755,6 +1739,37 @@ div.help {
font-weight: 100; font-weight: 100;
background-color: #c0c0c0; background-color: #c0c0c0;
cursor: default; } cursor: default; }
#loginPage .content .body .pinForm, #registrationPage .content .body .pinForm, #unlockPage .content .body .pinForm {
/*
.pinContainer {
display: flex;
flex-direction: row;
justify-content: space-between;
.pinDigit {
width: 15%;
}
}
*/ }
#loginPage .content .body .pinForm label, #registrationPage .content .body .pinForm label, #unlockPage .content .body .pinForm label {
display: inherit;
text-align: left; }
#loginPage .content .body .pinForm .pinValue, #registrationPage .content .body .pinForm .pinValue, #unlockPage .content .body .pinForm .pinValue {
font-family: clipperz-password; }
#loginPage .content .body .pinForm .pinValue::-webkit-input-placeholder, #registrationPage .content .body .pinForm .pinValue::-webkit-input-placeholder, #unlockPage .content .body .pinForm .pinValue::-webkit-input-placeholder {
font-family: clipperz-font; }
#loginPage .content .body .pinForm .pinValue:-moz-placeholder, #registrationPage .content .body .pinForm .pinValue:-moz-placeholder, #unlockPage .content .body .pinForm .pinValue:-moz-placeholder {
font-family: clipperz-font; }
#loginPage .content .body .pinForm .pinValue::-moz-placeholder, #registrationPage .content .body .pinForm .pinValue::-moz-placeholder, #unlockPage .content .body .pinForm .pinValue::-moz-placeholder {
font-family: clipperz-font; }
#loginPage .content .body .pinForm .pinValue:-ms-input-placeholder, #registrationPage .content .body .pinForm .pinValue:-ms-input-placeholder, #unlockPage .content .body .pinForm .pinValue:-ms-input-placeholder {
font-family: clipperz-font; }
#loginPage .content .body .pinForm .passphraseLogin, #registrationPage .content .body .pinForm .passphraseLogin, #unlockPage .content .body .pinForm .passphraseLogin {
font-size: .9em;
color: #ff9900;
text-decoration: underline;
cursor: pointer; }
#loginPage .content .other, #registrationPage .content .other, #unlockPage .content .other { #loginPage .content .other, #registrationPage .content .other, #unlockPage .content .other {
font-size: 24pt; font-size: 24pt;
padding: 20px; padding: 20px;
@ -2206,13 +2221,13 @@ span.count {
#extraFeaturesPanel .extraFeatureIndex footer { #extraFeaturesPanel .extraFeatureIndex footer {
font-size: 8pt; font-size: 8pt;
padding: 5px 5px 5px 5px; padding: 5px 5px 5px 5px;
border-top: 1px solid #999999; } border-top: 1px solid #999; }
#extraFeaturesPanel .extraFeatureIndex footer span { #extraFeaturesPanel .extraFeatureIndex footer span {
color: #999999; } color: #999; }
#extraFeaturesPanel .extraFeatureIndex footer span:after { #extraFeaturesPanel .extraFeatureIndex footer span:after {
content: ":"; } content: ":"; }
#extraFeaturesPanel .extraFeatureIndex footer a { #extraFeaturesPanel .extraFeatureIndex footer a {
color: #999999; color: #999;
text-decoration: none; text-decoration: none;
padding-left: 5px; padding-left: 5px;
font-weight: bold; } font-weight: bold; }
@ -2225,6 +2240,8 @@ span.count {
font-size: 20pt; } font-size: 20pt; }
#extraFeaturesPanel .extraFeatureContent .extraFeature .header p { #extraFeaturesPanel .extraFeatureContent .extraFeature .header p {
padding: 10px 0px; } padding: 10px 0px; }
#extraFeaturesPanel .extraFeatureContent .extraFeature .header strong {
font-weight: bold; }
#extraFeaturesPanel .extraFeatureContent .extraFeature form label { #extraFeaturesPanel .extraFeatureContent .extraFeature form label {
display: none; } display: none; }
#extraFeaturesPanel .extraFeatureContent .extraFeature form input { #extraFeaturesPanel .extraFeatureContent .extraFeature form input {
@ -2585,6 +2602,17 @@ span.count {
padding: 0 1em; padding: 0 1em;
text-decoration: underline; text-decoration: underline;
cursor: pointer; } cursor: pointer; }
#extraFeaturesPanel .extraFeatureContent .devicePIN .pinDigit {
display: inline-block;
width: 2em;
margin-right: 0.5em;
text-align: center; }
#extraFeaturesPanel .extraFeatureContent .devicePIN .pinValue {
display: inline-block;
width: 4em;
margin-right: 1em; }
#extraFeaturesPanel .extraFeatureContent .devicePIN :enabled {
border: 2px solid #ff9900; }
#extraFeaturesPanel .extraFeatureContent .dataImport .content { #extraFeaturesPanel .extraFeatureContent .dataImport .content {
display: block; display: block;
height: 100%; height: 100%;
@ -2994,7 +3022,7 @@ div.cardList ul {
padding-right: 0px; padding-right: 0px;
box-shadow: -4px 0px 3px -1px rgba(0, 0, 0, 0.2); } box-shadow: -4px 0px 3px -1px rgba(0, 0, 0, 0.2); }
div.cardList ul li.archived { div.cardList ul li.archived {
background-color: #eeeeee; background-color: #eee;
color: #999; } color: #999; }
div.cardList ul li .favicon { div.cardList ul li .favicon {
width: 48px; width: 48px;
@ -3081,7 +3109,7 @@ div.cardList.narrow {
content: ""; } content: ""; }
#cardDetailPage .view.archived, .cardDetail .view.archived { #cardDetailPage .view.archived, .cardDetail .view.archived {
background-color: #eeeeee; } background-color: #eee; }
#cardDetailPage .view .cardDetailToolbar, .cardDetail .view .cardDetailToolbar { #cardDetailPage .view .cardDetailToolbar, .cardDetail .view .cardDetailToolbar {
background-color: #1863a1; background-color: #1863a1;
color: white; } color: white; }
@ -3311,7 +3339,7 @@ div.cardList.narrow {
cursor: grab; cursor: grab;
cursor: -moz-grab; cursor: -moz-grab;
cursor: -webkit-grab; cursor: -webkit-grab;
background: repeating-linear-gradient(0deg, white, white 2px, #dddddd 2px, #dddddd 3px); background: repeating-linear-gradient(0deg, white, white 2px, #ddd 2px, #ddd 3px);
width: 28px; width: 28px;
height: 20px; height: 20px;
margin-left: 6px; margin-left: 6px;
@ -3537,7 +3565,7 @@ div.cardList.narrow {
min-width: 220px; min-width: 220px;
width: 80%; width: 80%;
max-width: 400px; max-width: 400px;
background-color: #333333; background-color: #333;
color: #fff; color: #fff;
-webkit-border-radius: 6px; -webkit-border-radius: 6px;
-moz-border-radius: 6px; -moz-border-radius: 6px;
@ -3552,7 +3580,7 @@ div.cardList.narrow {
margin-left: 0px; margin-left: 0px;
width: 0; width: 0;
height: 0; height: 0;
border-top: 5px solid #333333; border-top: 5px solid #333;
border-left: 5px solid transparent; border-left: 5px solid transparent;
border-right: 5px solid transparent; } border-right: 5px solid transparent; }
.passwordGenerator .passwordGeneratorBaloon form span { .passwordGenerator .passwordGeneratorBaloon form span {

File diff suppressed because one or more lines are too long

View File

@ -39,18 +39,18 @@ MochiKit.Base.update(Clipperz.PM.PIN, {
return this.__repr__(); return this.__repr__();
}, },
'CREDENTIALS': 'CLIPPERZ.CREDENTIALS', 'ENCRYPTED_PASSPHRASE_LENGTH': 1024,
'FAILURE_COUNT': 'CLIPPERZ.FAILED_LOGIN_COUNT', 'DEFAULT_PIN_LENGTH': 5,
'ALLOWED_RETRY': 3, 'ALLOWED_RETRY': 3,
'LS_USERNAME': 'clipperz.pin.username',
'LS_PASSPHRASE': 'clipperz.pin.passphrase',
'LS_FAILURE_COUNT': 'clipperz.pin.failureCount',
//------------------------------------------------------------------------- //-------------------------------------------------------------------------
'isSet': function () { 'isSet': function () {
return (this.storedCredentials() != null); return (localStorage[this.LS_USERNAME] && localStorage[this.LS_PASSPHRASE]);
},
'storedCredentials': function () {
return localStorage[this.CREDENTIALS];
}, },
//------------------------------------------------------------------------- //-------------------------------------------------------------------------
@ -59,7 +59,7 @@ MochiKit.Base.update(Clipperz.PM.PIN, {
var failureCount; var failureCount;
var result; var result;
failureCount = localStorage[this.FAILURE_COUNT]; failureCount = localStorage[this.LS_FAILURE_COUNT];
if (failureCount == null) { if (failureCount == null) {
failureCount = 0 failureCount = 0
@ -68,10 +68,10 @@ MochiKit.Base.update(Clipperz.PM.PIN, {
failureCount ++; failureCount ++;
if (failureCount < this.ALLOWED_RETRY) { if (failureCount < this.ALLOWED_RETRY) {
localStorage[this.FAILURE_COUNT] = failureCount; localStorage[this.LS_FAILURE_COUNT] = failureCount;
result = failureCount; result = failureCount;
} else { } else {
this.removeLocalCredentials(); this.disablePin();
result = -1; result = -1;
} }
@ -79,11 +79,11 @@ MochiKit.Base.update(Clipperz.PM.PIN, {
}, },
'resetFailedAttemptCount': function () { 'resetFailedAttemptCount': function () {
localStorage.removeItem(this.FAILURE_COUNT); localStorage.removeItem(this.LS_FAILURE_COUNT);
}, },
'failureCount': function () { 'failureCount': function () {
return localStorage[this.FAILURE_COUNT]; return localStorage[this.LS_FAILURE_COUNT];
}, },
//------------------------------------------------------------------------- //-------------------------------------------------------------------------
@ -93,36 +93,50 @@ MochiKit.Base.update(Clipperz.PM.PIN, {
}, },
'credentialsWithPIN': function(aPIN) { 'credentialsWithPIN': function(aPIN) {
var byteArrayValue; return {
var decryptedValue; 'username': localStorage[this.LS_USERNAME],
var result; 'passphrase': this.decryptPassphraseWithPin(aPIN, localStorage[this.LS_PASSPHRASE]),
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'};
} }
return result;
}, },
'setCredentialsWithPIN': function (aPIN, someCredentials) { 'encryptPassphraseWithPin': function(aPIN, aPassphrase) {
var encodedValue; var byteArrayPassphrase = new Clipperz.ByteArray(aPassphrase);
var byteArrayValue; // var hashedPassphrase = Clipperz.Crypto.SHA.sha_d256(ba) // ??? why would i hash the passphrase???
var encryptedValue; 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); byteArrayPassphrase.appendByte(0);
byteArrayValue = new Clipperz.ByteArray(encodedValue); byteArrayPassphrase.appendBytes(randomBytes.arrayValues());
encryptedValue = Clipperz.Crypto.AES.encrypt(this.deriveKeyFromPin(aPIN), byteArrayValue).toBase64String();
localStorage[this.CREDENTIALS] = encryptedValue; return Clipperz.Crypto.AES.encrypt(derivedKey, byteArrayPassphrase).toBase64String();
}, },
'removeLocalCredentials': function () { 'decryptPassphraseWithPin': function(aPIN, anEncryptedPassphrase) {
localStorage.removeItem(this.CREDENTIALS); var byteArrayEncryptedPassphrase = (new Clipperz.ByteArray()).appendBase64String(anEncryptedPassphrase);
localStorage.removeItem(this.FAILURE_COUNT); 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() { 'isLocalStorageSupported': function() {

View File

@ -403,7 +403,7 @@ Clipperz.Base.extend(Clipperz.PM.Proxy.Offline.DataStore, Object, {
); );
result['M2'] = M2; result['M2'] = M2;
result['accountInfo'] = aConnection['userData']['accountInfo']; result['accountInfo'] = aConnection['userData']['accountInfo'];
result['lock'] = '<<LOCK>>'; result['lock'] = (aConnection['userData']['lock']) ? aConnection['userData']['lock'] : '<<LOCK>>';
} else { } else {
throw new Error("Client checksum verification failed! Expected <" + M1 + ">, received <" + someParameters.parameters.M1 + ">.", "Error"); 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 // '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 () { render: function () {
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'}, [ return React.DOM.div({className:'extraFeature devicePIN'}, [
React.DOM.div({'className':'header'}, [ React.DOM.div({'className':'header'}, [
React.DOM.h1({}, "Device PIN"), 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.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) { return MochiKit.Base.bind(function (anEvent) {
var value = anEvent.target.value; 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) { if (anEvent.target.defaultValue != value) {
switch (anEvent.keyCode) { switch (anEvent.keyCode) {
case 9: // tab case 9: // tab
@ -80,7 +80,7 @@ console.log("HANDLE KEY DOWN", anEvent, anEvent.keyCode, value);
anEvent.target.defaultValue = anEvent.target.value; anEvent.target.defaultValue = anEvent.target.value;
break; break;
case 27: // escape case 27: // escape
console.log("ESCAPE"); // console.log("ESCAPE");
anEvent.target.value = anEvent.target.defaultValue; anEvent.target.value = anEvent.target.defaultValue;
break; break;
} }

View File

@ -45,12 +45,18 @@ Clipperz.PM.UI.Components.Pages.LoginPageClass = React.createClass({
return { return {
username: '', username: '',
passphrase: '', passphrase: '',
pin: '' pin: '',
}; };
}, },
//========================================================================= //=========================================================================
mode: function() {
return (this.props['mode'] == 'CREDENTIALS' || this.props['forceCredentials']) ? 'CREDENTIALS' : 'PIN';
},
//=========================================================================
handleChange: function (anEvent) { handleChange: function (anEvent) {
var refs = this.refs; var refs = this.refs;
var refName = MochiKit.Base.filter(function (aRefName) { return refs[aRefName].getDOMNode() == anEvent.target}, MochiKit.Base.keys(this.refs))[0]; 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() { pollForChanges: function() {
if (this.props.mode == 'CREDENTIALS') { if (this.mode() == 'CREDENTIALS') {
var newState; var newState;
var usernameValue = this.refs['username'].getDOMNode().value; var usernameValue = this.refs['username'].getDOMNode().value;
@ -102,8 +108,8 @@ Clipperz.PM.UI.Components.Pages.LoginPageClass = React.createClass({
return ( return (
((this.state['username'] != '') && (this.state['passphrase'] != '')) ((this.state['username'] != '') && (this.state['passphrase'] != ''))
|| // ||
(this.state['pin'] != '') // (this.state['pin'] != '')
) )
&& &&
!this.props['disabled']; !this.props['disabled'];
@ -121,9 +127,7 @@ Clipperz.PM.UI.Components.Pages.LoginPageClass = React.createClass({
]); ]);
}, },
handlePINSubmit: function (event) { submitPIN: function (event) {
event.preventDefault();
this.refs['pin'].getDOMNode().blur(); this.refs['pin'].getDOMNode().blur();
var credentials = { var credentials = {
@ -133,19 +137,82 @@ Clipperz.PM.UI.Components.Pages.LoginPageClass = React.createClass({
MochiKit.Signal.signal(Clipperz.Signal.NotificationCenter, 'doLogin', credentials); 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 () { pinForm: function () {
return React.DOM.form({'className':'pinForm pin', 'autoComplete':'off', 'onChange':this.handleChange, 'onSubmit':this.handlePINSubmit}, [ return React.DOM.form({
'className':'pinForm pin',
'autoComplete':'off',
'onSubmit': function(anEvent) {anEvent.preventDefault();},
}, [
React.DOM.div({'key':'pinFormDiv'},[ React.DOM.div({'key':'pinFormDiv'},[
React.DOM.label({'for':'pin'}, "pin"), React.DOM.label({'htmlFor':'pin'}, "Enter your PIN"),
React.DOM.input({'type':'text', 'name':'pin', 'ref':'pin', placeholder:"PIN", 'key':'pin', 'autocapitalize':'none'}) 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") // React.DOM.button({'key':'submitButton', 'type':'submit', 'disabled':this.props.disabled, 'className':'button'}, "login")
]); ]);
}, },
setInitialFocus: function () { setInitialFocus: function () {
if (this.props.mode == 'PIN') { if (this.mode() == 'PIN') {
this.refs['pin'].getDOMNode().select(); this.setState({
'pin': ''
})
this.refs['pin'].getDOMNode().focus();
} else { } else {
if (this.refs['username'].getDOMNode().value == '') { if (this.refs['username'].getDOMNode().value == '') {
this.refs['username'].getDOMNode().focus(); this.refs['username'].getDOMNode().focus();
@ -162,7 +229,6 @@ Clipperz.PM.UI.Components.Pages.LoginPageClass = React.createClass({
}, },
render: function() { render: function() {
//console.log("LOGIN PAGE", this.props);
// var registrationLink = React.DOM.footer({'key':'registrationLink', 'className':'registrationLink'}, [ // var registrationLink = React.DOM.footer({'key':'registrationLink', 'className':'registrationLink'}, [
// React.DOM.a({'key':'signup', 'onClick':this.handleRegistrationLinkClick}, "Sign up") // 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({'key':'formWrapper', 'className':'form body'}, [
React.DOM.div({'className':'bodyContent'}, [ 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, 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) { handleChange: function (anEvent) {
var newState = {}; var newState = {};
@ -49,27 +55,58 @@ Clipperz.PM.UI.Components.Pages.UnlockPageClass = React.createClass({
this.setState(newState); 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) { handlePassphraseSubmit: function (event) {
event.preventDefault(); event.preventDefault();
this.refs['passphrase'].getDOMNode().blur(); 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() { resetUnlockForm: function() {
if (this.mode() == 'CREDENTIALS') {
this.refs['passphrase'].getDOMNode().value = ''; this.refs['passphrase'].getDOMNode().value = '';
this.replaceState(this.getInitialState()); 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 () { setInitialFocus: function () {
if (this.props.mode == 'PIN') { if (this.mode() == 'PIN') {
this.refs['pin'].getDOMNode().select(); this.refs['pin'].getDOMNode().focus();
} else { } 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 () { shouldEnableUnlockButton: function () {
var result; var result;
@ -103,6 +169,10 @@ Clipperz.PM.UI.Components.Pages.UnlockPageClass = React.createClass({
!this.props['disabled']; !this.props['disabled'];
}, },
// componentDidUpdate: function() {
// this.setInitialFocus();
// },
render: function() { render: function() {
return React.DOM.div({'key':'unlockForm', 'className':'unlockForm content ' + this.props['style']}, [ return React.DOM.div({'key':'unlockForm', 'className':'unlockForm content ' + this.props['style']}, [
Clipperz.PM.UI.Components.AccountStatus(MochiKit.Base.update(this.props['proxyInfo'])), 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({'key':'formWrapper', 'className':'form body'}, [
React.DOM.div({'className':'bodyContent'}, [ 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'}, [ 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.p({}, "Manage your OTPs.")
// ]) // ])
]), ]),
/*
React.DOM.li({'key':'account_3', 'onClick':this.toggleExtraFeatureComponent('DevicePIN')}, [ React.DOM.li({'key':'account_3', 'onClick':this.toggleExtraFeatureComponent('DevicePIN')}, [
React.DOM.h2({}, "Device PIN"), React.DOM.h2({}, "Device PIN"),
React.DOM.div({}, [ // 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.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.li({'key':'account_5', 'onClick':this.toggleExtraFeatureComponent('DeleteAccount'), 'className':(this.state['extraFeatureComponentName'] == 'DeleteAccount') ? 'selected' : ''}, [
React.DOM.h2({}, "Delete account"), React.DOM.h2({}, "Delete account"),
// React.DOM.div({}, [ // React.DOM.div({}, [

View File

@ -293,7 +293,7 @@ MochiKit.Base.update(Clipperz.PM.UI.ImportContext.prototype, {
if (isUploadingFile) { if (isUploadingFile) {
var isExportContent; var isExportContent;
isExportContent = new RegExp('.*<textarea>(.*)<\/textarea>.*', 'g'); isExportContent = new RegExp('[\\s\\S]*<textarea>([\\s\\S]*)<\/textarea>[\\s\\S]*', 'g');
if (isExportContent.test(aValue)) { if (isExportContent.test(aValue)) {
textarea = MochiKit.DOM.TEXTAREA(); textarea = MochiKit.DOM.TEXTAREA();
textarea.innerHTML = aValue.replace(isExportContent, '$1'); textarea.innerHTML = aValue.replace(isExportContent, '$1');

View File

@ -68,6 +68,7 @@ Clipperz.PM.UI.MainController = function() {
'doLogin', 'registerNewUser', 'showRegistrationForm', 'goBack', 'doLogin', 'registerNewUser', 'showRegistrationForm', 'goBack',
'logout', 'logout',
'enableLock', 'disableLock', 'unlock', 'enableLock', 'disableLock', 'unlock',
'updatePIN', 'disablePIN', 'forcePassphraseLogin', 'forcePassphraseUnlock',
'changePassphrase', 'deleteAccount', 'changePassphrase', 'deleteAccount',
/*'updateUserPreferences',*/ 'setPreference', /*'updateUserPreferences',*/ 'setPreference',
'updateOTPListAndDetails', 'createNewOTP', 'deleteOTPs', 'changeOTPLabel', 'updateOTPListAndDetails', 'createNewOTP', 'deleteOTPs', 'changeOTPLabel',
@ -328,6 +329,16 @@ Clipperz.log("THE BROWSER IS OFFLINE");
MochiKit.Async.callLater(0.5, MochiKit.Base.method(registrationPage, 'setInitialFocus')); 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) { doLogin_handler: function (event) {
@ -411,35 +422,57 @@ Clipperz.log("THE BROWSER IS OFFLINE");
return deferredResult; return deferredResult;
}, },
unlock_handler: function(aPassphrase) { unlock_handler: function(aCredential, aCredentialType) {
var deferredResult; var deferredResult;
var passphrase;
var user = this.user(); var user = this.user();
var unlockPage = this.pages()['unlockPage']; var unlockPage = this.pages()['unlockPage'];
var overlay = this.overlay(); var overlay = this.overlay();
passphrase = (aCredentialType=='PIN') ? Clipperz.PM.PIN.credentialsWithPIN(aCredential)['passphrase'] : aCredential;
overlay.show("validating…"); overlay.show("validating…");
deferredResult = new Clipperz.Async.Deferred('MainController.unlock_handler', {trace:false}); deferredResult = new Clipperz.Async.Deferred('MainController.unlock_handler', {trace:false});
deferredResult.addMethod(unlockPage, 'setProps', {'disabled': true}); deferredResult.addMethod(unlockPage, 'setProps', {'disabled': true});
deferredResult.addMethod(user, 'unlock', function() { return MochiKit.Async.succeed(aPassphrase); }); deferredResult.addMethod(user, 'unlock', function() { return MochiKit.Async.succeed(passphrase); });
deferredResult.addErrback(function (aValue) { deferredResult.addErrback(MochiKit.Base.bind(function (aValue) {
var innerDeferredResult; 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 = 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(unlockPage, 'setInitialFocus');
innerDeferredResult.addMethod(overlay, 'failed', "", 1); innerDeferredResult.addMethod(overlay, 'failed', errorMessage, 1);
innerDeferredResult.addCallback(MochiKit.Async.fail, aValue); innerDeferredResult.addCallback(MochiKit.Async.fail, aValue);
innerDeferredResult.callback(); innerDeferredResult.callback();
return aValue; return aValue;
}); }, this));
if (aCredentialType=='PIN') {
deferredResult.addMethod(Clipperz.PM.PIN, 'resetFailedAttemptCount');
}
deferredResult.addMethod(this, 'updateUserPreferences'); deferredResult.addMethod(this, 'updateUserPreferences');
deferredResult.addMethod(this, 'moveInPage', this.currentPage(), 'mainPage'); deferredResult.addMethod(this, 'moveInPage', this.currentPage(), 'mainPage');
deferredResult.addMethod(this, 'refreshUI'); deferredResult.addMethod(this, 'refreshUI');
deferredResult.addMethod(unlockPage, 'setProps', {'disabled': false}); deferredResult.addMethod(unlockPage, 'setProps', {
'disabled': false,
'forceCredentials': false,
});
deferredResult.addMethod(unlockPage, 'resetUnlockForm'); deferredResult.addMethod(unlockPage, 'resetUnlockForm');
deferredResult.addCallback(MochiKit.Signal.signal, Clipperz.Signal.NotificationCenter, 'enableLock'); deferredResult.addCallback(MochiKit.Signal.signal, Clipperz.Signal.NotificationCenter, 'enableLock');
deferredResult.addMethod(overlay, 'done', "", 0.5); deferredResult.addMethod(overlay, 'done', "", 0.5);
@ -448,7 +481,7 @@ Clipperz.log("THE BROWSER IS OFFLINE");
return deferredResult; 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 // TODO: check if passphrase is correct by try/catch on decrypting something
// this.moveOutPage(this.currentPage(), 'mainPage'); // this.moveOutPage(this.currentPage(), 'mainPage');
// TODO: check why the unlock form keeps the value stored (doesn't happen with the login form...) // 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'); 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 () { resetLockTimeout: function () {
if (this.user()) { if (this.user()) {
return Clipperz.Async.callbacks("MainController.resetLockTimeout", [ return Clipperz.Async.callbacks("MainController.resetLockTimeout", [
MochiKit.Base.method(this.user(), 'getPreference', 'lock'), MochiKit.Base.method(this.user(), 'getPreference', 'lock'),
MochiKit.Base.bind(function (someLockInfo) { MochiKit.Base.bind(function (someLockInfo) {
if (this._lockTimeout) { if (this._lockTimeout) {
// console.log("clearing previous lock timer");
clearTimeout(this._lockTimeout); clearTimeout(this._lockTimeout);
} }
@ -990,7 +1040,7 @@ Clipperz.log("THE BROWSER IS OFFLINE");
errorMessage = "failure"; errorMessage = "failure";
} else { } else {
if ('pin' in anEvent) { if ('pin' in anEvent) {
errorCount = Clipperz.PM.PIN.recordFailedAttempt(); var errorCount = Clipperz.PM.PIN.recordFailedAttempt();
if (errorCount == -1) { if (errorCount == -1) {
errorMessage = "PIN resetted"; errorMessage = "PIN resetted";
} }
@ -1170,7 +1220,8 @@ Clipperz.log("THE BROWSER IS OFFLINE");
'style': this.mediaQueryStyle(), 'style': this.mediaQueryStyle(),
'isTouchDevice': this.isTouchDevice(), 'isTouchDevice': this.isTouchDevice(),
'isDesktop': this.isDesktop(), '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') { if (aPageName == 'loginPage') {
extraProperties = { extraProperties = {
'mode': 'CREDENTIALS', 'mode': this.loginMode(),
'isNewUserRegistrationAvailable': Clipperz.PM.Proxy.defaultProxy.canRegisterNewUsers(), 'isNewUserRegistrationAvailable': Clipperz.PM.Proxy.defaultProxy.canRegisterNewUsers(),
'disabled': false, 'disabled': false,
'proxyInfo': this.proxyInfo(), 'proxyInfo': this.proxyInfo(),
}; };
} else if (aPageName == 'unlockPage') {
extraProperties = {
'mode': this.loginMode(),
}
} else if (aPageName == 'registrationPage') { } else if (aPageName == 'registrationPage') {
} else if (aPageName == 'mainPage') { } else if (aPageName == 'mainPage') {
extraProperties = { extraProperties = {

View File

@ -51,8 +51,8 @@
"MochiKit/Selector.js", "MochiKit/Selector.js",
"-- MochiKit/Visual.js", "-- MochiKit/Visual.js",
"-- React/react-0.13.3.js", "React/react-0.13.3.js",
"React/react.min-0.13.3.js", "-- React/react.min-0.13.3.js",
"-- #React/react-with-addons-0.13.1.js", "-- #React/react-with-addons-0.13.1.js",
"-- #React/react-with-addons.min-0.13.1.js", "-- #React/react-with-addons.min-0.13.1.js",

View File

@ -305,6 +305,43 @@ refer to http://www.clipperz.com.
} }
} }
} }
.pinForm {
/*
.pinContainer {
display: flex;
flex-direction: row;
justify-content: space-between;
.pinDigit {
width: 15%;
}
}
*/
label {
display: inherit;
text-align: left;
}
.pinValue {
font-family: clipperz-password;
@include placeholder {
font-family: clipperz-font;
}
}
.passphraseLogin {
font-size: .9em;
color: $clipperz-orange;
text-decoration: underline;
cursor:pointer;
}
}
} }
.other { .other {

View File

@ -251,6 +251,10 @@ refer to http://www.clipperz.com.
p { p {
padding: 10px 0px; padding: 10px 0px;
} }
strong {
font-weight: bold;
}
} }
form { form {
@ -706,6 +710,25 @@ refer to http://www.clipperz.com.
} }
} }
.devicePIN {
.pinDigit {
display: inline-block;
width: 2em;
margin-right: 0.5em;
text-align: center;
}
.pinValue {
display: inline-block;
width: 4em;
margin-right: 1em;
}
:enabled {
border:2px solid $clipperz-orange;
}
}
.dataImport { .dataImport {
.content { .content {