Skip setting passphrase in setup

This commit is contained in:
Tankred Hase 2014-09-12 17:11:05 +02:00
parent da246d4a03
commit 50f0bf951e
9 changed files with 172 additions and 210 deletions

1
.gitignore vendored
View File

@ -10,3 +10,4 @@ test/integration/src/
src/lib/*.js
src/js/crypto/aes-gcm.js
src/js/crypto/util.js
.elasticbeanstalk/

View File

@ -9,9 +9,8 @@ define(function(require) {
states = {
IDLE: 1,
SET_PASSPHRASE: 2,
PROCESSING: 3,
DONE: 4
PROCESSING: 2,
DONE: 3
};
$scope.state.ui = states.IDLE; // initial state
@ -37,9 +36,9 @@ define(function(require) {
};
/**
* Continue to set passphrase screen for keygen
* Continue to keygen
*/
$scope.setPassphrase = function() {
$scope.generateKey = function() {
if (!$scope.state.agree) {
$scope.onError({
message: termsMsg
@ -49,8 +48,29 @@ define(function(require) {
// sing up to newsletter
$scope.signUpToNewsletter();
// go to set passphrase screen
$scope.setState(states.SET_PASSPHRASE);
// go to set keygen screen
$scope.setState(states.PROCESSING);
setTimeout(function() {
emailDao.unlock({
passphrase: undefined // generate key without passphrase
}, function(err) {
if (err) {
$scope.setState(states.IDLE);
$scope.onError(err);
return;
}
appController._auth.storeCredentials(function(err) {
if (err) {
return $scope.onError(err);
}
$location.path('/desktop');
$scope.$apply();
});
});
}, 500);
};
/**
@ -87,86 +107,6 @@ define(function(require) {
xhr.send(formData);
};
/*
* Taken from jQuery validate.password plug-in 1.0
* http://bassistance.de/jquery-plugins/jquery-plugin-validate.password/
*
* Copyright (c) 2009 Jörn Zaefferer
*
* Licensed under the MIT
* http://www.opensource.org/licenses/mit-license.php
*/
$scope.checkPassphraseQuality = function() {
var passphrase = $scope.state.passphrase;
$scope.passphraseRating = 0;
var LOWER = /[a-z]/,
UPPER = /[A-Z]/,
DIGIT = /[0-9]/,
DIGITS = /[0-9].*[0-9]/,
SPECIAL = /[^a-zA-Z0-9]/,
SAME = /^(.)\1+$/;
function uncapitalize(str) {
return str.substring(0, 1).toLowerCase() + str.substring(1);
}
if (!passphrase) {
// no rating for empty passphrase
$scope.passphraseMsg = '';
return;
}
if (passphrase.length < 8 || SAME.test(passphrase)) {
$scope.passphraseMsg = 'Very weak';
return;
}
var lower = LOWER.test(passphrase),
upper = UPPER.test(uncapitalize(passphrase)),
digit = DIGIT.test(passphrase),
digits = DIGITS.test(passphrase),
special = SPECIAL.test(passphrase);
if (lower && upper && digit || lower && digits || upper && digits || special) {
$scope.passphraseMsg = 'Strong';
$scope.passphraseRating = 3;
} else if (lower && upper || lower && digit || upper && digit) {
$scope.passphraseMsg = 'Good';
$scope.passphraseRating = 2;
} else {
$scope.passphraseMsg = 'Weak';
$scope.passphraseRating = 1;
}
};
$scope.confirmPassphrase = function() {
var passphrase = $scope.state.passphrase;
$scope.setState(states.PROCESSING);
setTimeout(function() {
emailDao.unlock({
passphrase: (passphrase) ? passphrase : undefined
}, function(err) {
if (err) {
$scope.setState(states.SET_PASSPHRASE);
$scope.onError(err);
return;
}
appController._auth.storeCredentials(function(err) {
if (err) {
return $scope.onError(err);
}
$location.path('/desktop');
$scope.$apply();
});
});
}, 500);
};
$scope.setState = function(state) {
$scope.state.ui = state;
};

View File

@ -19,6 +19,7 @@ define(function(require) {
$scope.newPassphrase = undefined;
$scope.oldPassphrase = undefined;
$scope.confirmation = undefined;
$scope.passphraseMsg = undefined;
}
};
@ -30,6 +31,59 @@ define(function(require) {
// scope functions
//
/*
* Taken from jQuery validate.password plug-in 1.0
* http://bassistance.de/jquery-plugins/jquery-plugin-validate.password/
*
* Copyright (c) 2009 Jörn Zaefferer
*
* Licensed under the MIT
* http://www.opensource.org/licenses/mit-license.php
*/
$scope.checkPassphraseQuality = function() {
var passphrase = $scope.newPassphrase;
$scope.passphraseRating = 0;
var LOWER = /[a-z]/,
UPPER = /[A-Z]/,
DIGIT = /[0-9]/,
DIGITS = /[0-9].*[0-9]/,
SPECIAL = /[^a-zA-Z0-9]/,
SAME = /^(.)\1+$/;
function uncapitalize(str) {
return str.substring(0, 1).toLowerCase() + str.substring(1);
}
if (!passphrase) {
// no rating for empty passphrase
$scope.passphraseMsg = '';
return;
}
if (passphrase.length < 8 || SAME.test(passphrase)) {
$scope.passphraseMsg = 'Very weak';
return;
}
var lower = LOWER.test(passphrase),
upper = UPPER.test(uncapitalize(passphrase)),
digit = DIGIT.test(passphrase),
digits = DIGITS.test(passphrase),
special = SPECIAL.test(passphrase);
if (lower && upper && digit || lower && digits || upper && digits || special) {
$scope.passphraseMsg = 'Strong';
$scope.passphraseRating = 3;
} else if (lower && upper || lower && digit || upper && digit) {
$scope.passphraseMsg = 'Good';
$scope.passphraseRating = 2;
} else {
$scope.passphraseMsg = 'Weak';
$scope.passphraseRating = 1;
}
};
$scope.setPassphrase = function() {
var keyId = pgp.getKeyParams()._id;
keychain.lookupPrivateKey(keyId, function(err, savedKey) {

View File

@ -104,10 +104,6 @@
}
}
.passphrase-label-ok {
color: green;
}
.popover-info {
display: none; // hide on mobile
}
@ -179,7 +175,7 @@
input[type="text"] {
flex: 2;
}
input[type="number"] {
flex: 1;
margin-right: 0;

View File

@ -1,5 +1,14 @@
.view-set-passphrase {
p {
margin: 40px auto 0;
width: 90%;
}
.passphrase-label-ok {
color: green;
}
.inputs {
margin: 40px 60px 30px;
@ -9,17 +18,21 @@
}
table {
margin: 50px auto 60px auto;
margin: 40px auto 60px auto;
td {
padding-top: 15px;
padding-top: 16px;
&:first-child {
text-align: right;
padding-right: 15px;
padding-right: 16px;
font-weight: bold;
}
}
td.no-padding {
padding-top: 0;
}
}
}

View File

@ -21,28 +21,12 @@
<div>
<button wo-touch="importKey()" class="btn btn-alt">Import existing key</button>
<button type="submit" wo-touch="setPassphrase()" class="btn"tabindex="3">Generate new key</button>
<button type="submit" wo-touch="generateKey()" class="btn"tabindex="3">Generate new key</button>
</div>
</form>
</div>
<div ng-show="state.ui === 2">
<p><b>Set passphrase.</b> You can set a passphrase to protect your key on disk. This must be entered everytime you start the app. For no passphrase just press continue.</p>
<form>
<div>
<label class="input-error-message" ng-class="{'passphrase-label-ok': passphraseRating >= 2}">{{passphraseMsg}}</label><br>
<input class="input-text" type="password" ng-model="state.passphrase" ng-change="checkPassphraseQuality()" placeholder="Enter passphrase" tabindex="1" focus-me="true">
<input class="input-text" type="password" ng-model="state.confirmation" ng-class="{'input-text-error': (state.confirmation || state.passphrase) && state.confirmation !== state.passphrase}" placeholder="Confirm passphrase" tabindex="2">
</div>
<div>
<button type="submit" wo-touch="confirmPassphrase()" class="btn" ng-disabled="(state.confirmation || state.passphrase) && state.confirmation !== state.passphrase" tabindex="3">Continue</button>
</div>
</form>
</div>
<div ng-show="state.ui === 3">
<p><b>Generating key.</b> Please stand by. This can take a while...</p>
<div class="working">
<span class="spinner"></span>

View File

@ -6,16 +6,21 @@
<div class="content">
<div class="dialog view-set-passphrase">
<p>You can set a passphrase to protect your key on disk. This must be entered everytime you start the app.</p>
<table>
<tbody>
<tr>
<td><label>Current passphrase</label></td>
<td><input class="input-text" type="password" ng-model="oldPassphrase" ng-change="checkPassphraseQuality()" tabindex="1" focus-me="true"></td>
<td><input class="input-text" type="password" ng-model="oldPassphrase" tabindex="1" focus-me="true"></td>
</tr>
<tr>
<td><label>New passphrase</label></td>
<td><input class="input-text" type="password" ng-model="newPassphrase" ng-change="checkPassphraseQuality()" tabindex="2"></td>
<td></td>
<td><label class="input-error-message" ng-class="{'passphrase-label-ok': passphraseRating >= 2}">{{passphraseMsg}}</label></td>
</tr>
<tr>
<td class="no-padding"><label>New passphrase</label></td>
<td class="no-padding"><input class="input-text" type="password" ng-model="newPassphrase" ng-change="checkPassphraseQuality()" tabindex="2"></td>
</tr>
<tr>
<td><label>Confirm passphrase</label></td>

View File

@ -14,7 +14,6 @@ define(function(require) {
var scope, ctrl, location, origEmailDao, emailDaoMock,
origAuth, authMock,
emailAddress = 'fred@foo.com',
passphrase = 'asd',
keyId, expectedKeyId,
cryptoMock;
@ -57,7 +56,6 @@ define(function(require) {
describe('initial state', function() {
it('should be well defined', function() {
expect(scope.confirmPassphrase).to.exist;
expect(scope.state.ui).to.equal(1);
});
});
@ -139,49 +137,7 @@ define(function(require) {
});
});
describe('check passphrase quality', function() {
it('should be too short', function() {
scope.state.passphrase = '&§DG36';
scope.checkPassphraseQuality();
expect(scope.passphraseMsg).to.equal('Very weak');
expect(scope.passphraseRating).to.equal(0);
});
it('should be very weak', function() {
scope.state.passphrase = 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa';
scope.checkPassphraseQuality();
expect(scope.passphraseMsg).to.equal('Very weak');
expect(scope.passphraseRating).to.equal(0);
});
it('should be weak', function() {
scope.state.passphrase = 'asdfasdfasdfasdfasdfasdfasdfasdfasdfasdfasdf';
scope.checkPassphraseQuality();
expect(scope.passphraseMsg).to.equal('Weak');
expect(scope.passphraseRating).to.equal(1);
});
it('should be good', function() {
scope.state.passphrase = 'asdfasdfasdfasdfasdfasdfasdfasdfasdfasdfasdf5';
scope.checkPassphraseQuality();
expect(scope.passphraseMsg).to.equal('Good');
expect(scope.passphraseRating).to.equal(2);
});
it('should be strong', function() {
scope.state.passphrase = '&§DG36abcd';
scope.checkPassphraseQuality();
expect(scope.passphraseMsg).to.equal('Strong');
expect(scope.passphraseRating).to.equal(3);
});
});
describe('setPassphrase', function() {
describe('generate key', function() {
var signUpToNewsletterStub;
beforeEach(function() {
signUpToNewsletterStub = sinon.stub(scope, 'signUpToNewsletter');
@ -195,79 +151,49 @@ define(function(require) {
scope.onError = function(err) {
expect(err.message).to.contain('Terms');
expect(scope.state.ui).to.equal(1);
expect(signUpToNewsletterStub.called).to.be.false;
done();
};
scope.setPassphrase();
scope.generateKey();
});
it('should continue', function(done) {
it('should fail due to error in emailDao.unlock', function(done) {
scope.state.agree = true;
var setStateStub = sinon.stub(scope, 'setState', function(state) {
expect(setStateStub.calledOnce).to.be.true;
expect(signUpToNewsletterStub.calledOnce).to.be.true;
expect(state).to.equal(2);
emailDaoMock.unlock.withArgs({
passphrase: undefined
}).yields(new Error());
authMock.storeCredentials.yields();
scope.onError = function(err) {
expect(err).to.exist;
expect(scope.state.ui).to.equal(1);
expect(signUpToNewsletterStub.called).to.be.true;
done();
});
};
scope.setPassphrase();
scope.generateKey();
expect(scope.state.ui).to.equal(2);
});
});
describe('confirm passphrase', function() {
var setStateStub;
it('should unlock crypto', function(done) {
scope.state.passphrase = passphrase;
scope.state.confirmation = passphrase;
scope.state.agree = true;
emailDaoMock.unlock.withArgs({
passphrase: passphrase
passphrase: undefined
}).yields();
authMock.storeCredentials.yields();
scope.$apply = function() {
expect(scope.state.ui).to.equal(2);
expect(location.$$path).to.equal('/desktop');
expect(emailDaoMock.unlock.calledOnce).to.be.true;
done();
};
scope.confirmPassphrase();
});
it('should not do anything matching passphrases', function() {
scope.state.passphrase = 'a';
scope.state.confirmation = 'b';
scope.confirmPassphrase();
});
it('should not work when keypair generation fails', function(done) {
scope.state.passphrase = passphrase;
scope.state.confirmation = passphrase;
emailDaoMock.unlock.withArgs({
passphrase: passphrase
}).yields(new Error('asd'));
setStateStub = sinon.stub(scope, 'setState', function(state) {
if (setStateStub.calledOnce) {
expect(state).to.equal(3);
} else if (setStateStub.calledTwice) {
expect(state).to.equal(2);
expect(emailDaoMock.unlock.calledOnce).to.be.true;
scope.setState.restore();
}
});
scope.onError = function(err) {
expect(err.message).to.equal('asd');
done();
};
scope.confirmPassphrase();
scope.generateKey();
});
});

View File

@ -79,5 +79,48 @@ define(function(require) {
scope.setPassphrase();
});
});
describe('check passphrase quality', function() {
it('should be too short', function() {
scope.newPassphrase = '&§DG36';
scope.checkPassphraseQuality();
expect(scope.passphraseMsg).to.equal('Very weak');
expect(scope.passphraseRating).to.equal(0);
});
it('should be very weak', function() {
scope.newPassphrase = 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa';
scope.checkPassphraseQuality();
expect(scope.passphraseMsg).to.equal('Very weak');
expect(scope.passphraseRating).to.equal(0);
});
it('should be weak', function() {
scope.newPassphrase = 'asdfasdfasdfasdfasdfasdfasdfasdfasdfasdfasdf';
scope.checkPassphraseQuality();
expect(scope.passphraseMsg).to.equal('Weak');
expect(scope.passphraseRating).to.equal(1);
});
it('should be good', function() {
scope.newPassphrase = 'asdfasdfasdfasdfasdfasdfasdfasdfasdfasdfasdf5';
scope.checkPassphraseQuality();
expect(scope.passphraseMsg).to.equal('Good');
expect(scope.passphraseRating).to.equal(2);
});
it('should be strong', function() {
scope.newPassphrase = '&§DG36abcd';
scope.checkPassphraseQuality();
expect(scope.passphraseMsg).to.equal('Strong');
expect(scope.passphraseRating).to.equal(3);
});
});
});
});