diff --git a/.gitignore b/.gitignore index da695ab..d9c457d 100644 --- a/.gitignore +++ b/.gitignore @@ -10,3 +10,4 @@ test/integration/src/ src/lib/*.js src/js/crypto/aes-gcm.js src/js/crypto/util.js +.elasticbeanstalk/ diff --git a/src/js/controller/login-initial.js b/src/js/controller/login-initial.js index 02c896d..f2e2246 100644 --- a/src/js/controller/login-initial.js +++ b/src/js/controller/login-initial.js @@ -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; }; diff --git a/src/js/controller/set-passphrase.js b/src/js/controller/set-passphrase.js index 7b1a98b..726c78b 100644 --- a/src/js/controller/set-passphrase.js +++ b/src/js/controller/set-passphrase.js @@ -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) { diff --git a/src/sass/views/_login.scss b/src/sass/views/_login.scss index 96c075c..361162a 100644 --- a/src/sass/views/_login.scss +++ b/src/sass/views/_login.scss @@ -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; diff --git a/src/sass/views/_set-passphrase.scss b/src/sass/views/_set-passphrase.scss index 33d19a0..d7e5f6f 100644 --- a/src/sass/views/_set-passphrase.scss +++ b/src/sass/views/_set-passphrase.scss @@ -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; + } } } \ No newline at end of file diff --git a/src/tpl/login-initial.html b/src/tpl/login-initial.html index 4d1c7ab..9db88d3 100644 --- a/src/tpl/login-initial.html +++ b/src/tpl/login-initial.html @@ -21,28 +21,12 @@
- +
-

Set passphrase. 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.

- -
-
-
- - -
- -
- -
-
-
- -

Generating key. Please stand by. This can take a while...

diff --git a/src/tpl/set-passphrase.html b/src/tpl/set-passphrase.html index b98582d..34e5cbf 100644 --- a/src/tpl/set-passphrase.html +++ b/src/tpl/set-passphrase.html @@ -6,16 +6,21 @@
+

You can set a passphrase to protect your key on disk. This must be entered everytime you start the app.

- + - - + + + + + + diff --git a/test/unit/login-initial-ctrl-test.js b/test/unit/login-initial-ctrl-test.js index 4e0d70d..b6c6a87 100644 --- a/test/unit/login-initial-ctrl-test.js +++ b/test/unit/login-initial-ctrl-test.js @@ -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(); }); }); diff --git a/test/unit/set-passphrase-ctrl-test.js b/test/unit/set-passphrase-ctrl-test.js index 5c4062b..42e0f37 100644 --- a/test/unit/set-passphrase-ctrl-test.js +++ b/test/unit/set-passphrase-ctrl-test.js @@ -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); + }); + }); + }); }); \ No newline at end of file