From 60342b3902e6b786c4855b0726417ea9f58aece4 Mon Sep 17 00:00:00 2001 From: Tankred Hase Date: Fri, 10 Jan 2014 21:35:34 +0100 Subject: [PATCH 1/8] work in progress --- src/js/controller/write.js | 83 +++++++++++++++++++++++++++++--------- src/tpl/write.html | 18 ++++++--- 2 files changed, 78 insertions(+), 23 deletions(-) diff --git a/src/js/controller/write.js b/src/js/controller/write.js index 9771a79..7ec76a5 100644 --- a/src/js/controller/write.js +++ b/src/js/controller/write.js @@ -39,7 +39,9 @@ define(function(require) { function resetFields() { $scope.writerTitle = 'New email'; - $scope.to = ''; + $scope.to = [{ + address: '' + }]; $scope.subject = ''; $scope.body = ''; $scope.ciphertextPreview = ''; @@ -73,43 +75,59 @@ define(function(require) { // Editing headers // - $scope.verifyTo = function() { - if (!$scope.to) { - resetDisplay(); + $scope.onAddressUpdate = function(recipient) { + var to = $scope.to, + address = recipient.address; + + // handle number of email inputs for multiple recipients + if (address.indexOf(' ') !== -1) { + recipient.address = address.replace(' ', ''); + to.push({ + address: '' + }); + } else if (address.length === 0 && to.length > 1) { + to.splice(to.indexOf(recipient), 1); + } + + verify(recipient); + }; + + function verify(recipient) { + // set display to insecure while fetching keys + recipient.key = undefined; + recipient.secure = false; + + // verify email address + if (!util.validateEmailAddress(recipient.address)) { + recipient.secure = undefined; return; } - // set display to insecure while fetching keys - $scope.toKey = undefined; - displayInsecure(); // check if to address is contained in known public keys - emailDao._keychain.getReceiverPublicKey($scope.to, function(err, key) { + emailDao._keychain.getReceiverPublicKey(recipient.address, function(err, key) { if (err) { $scope.onError(err); return; } // compare again since model could have changed during the roundtrip - if (key && key.userId === $scope.to) { - $scope.toKey = key; - displaySecure(); + if (key && key.userId === recipient.address) { + recipient.key = key; + recipient.secure = true; $scope.$apply(); } }); - }; + } function resetDisplay() { - $scope.toSecure = undefined; $scope.sendBtnText = undefined; } function displaySecure() { - $scope.toSecure = true; $scope.sendBtnText = 'Send securely'; } function displayInsecure() { - $scope.toSecure = false; $scope.sendBtnText = 'Invite & send securely'; } @@ -254,16 +272,45 @@ define(function(require) { link: function(scope, elm, attrs) { var model = $parse(attrs.autoSize); scope.$watch(model, function(value) { - if (!value) { - return; + var width; + + if (value.length < 12) { + width = (14 * 8) + 'px'; + } else { + width = ((value.length + 2) * 8) + 'px'; } - var width = ((value.length + 2) * 8) + 'px'; elm.css('width', width); }); } }; }); + ngModule.directive('addressInput', function($timeout, $parse) { + return { + //scope: true, // optionally create a child scope + link: function(scope, element, attrs) { + element.bind('keydown', function(e) { + if (e.keyCode === 32) { + // space -> go to next input + e.preventDefault(); + element[0].parent[0].nextSibling[0].children[0].focus(); + } + scope.$apply(); + }); + + + var model = $parse(attrs.focusMe); + scope.$watch(model, function(value) { + if (value === true) { + $timeout(function() { + element[0].focus(); + }, 100); + } + }); + } + }; + }); + return WriteCtrl; }); \ No newline at end of file diff --git a/src/tpl/write.html b/src/tpl/write.html index 5f00615..2ce5e1b 100644 --- a/src/tpl/write.html +++ b/src/tpl/write.html @@ -9,17 +9,25 @@

To: - + + + + +

Cc: - + +

+

+ Bcc: +

- +
-

+

-----BEGIN ENCRYPTED PREVIEW-----
{{ciphertextPreview}}
-----END ENCRYPTED PREVIEW-----

@@ -35,7 +43,7 @@
- +
From 6d0e562351e9a345d6cceb29cf3abda9625e5dd3 Mon Sep 17 00:00:00 2001 From: Tankred Hase Date: Mon, 13 Jan 2014 16:42:10 +0100 Subject: [PATCH 2/8] implement field jumping on spaceand tab press --- src/js/controller/write.js | 39 +++++++++++++++++++------------------- src/tpl/write.html | 18 ++++++++++-------- 2 files changed, 30 insertions(+), 27 deletions(-) diff --git a/src/js/controller/write.js b/src/js/controller/write.js index 7ec76a5..56b9a3b 100644 --- a/src/js/controller/write.js +++ b/src/js/controller/write.js @@ -30,7 +30,7 @@ define(function(require) { fillFields(replyTo); $scope.updatePreview(); - $scope.verifyTo(); + verify($scope.to[0]); }, close: function() { this.open = false; @@ -42,6 +42,12 @@ define(function(require) { $scope.to = [{ address: '' }]; + $scope.cc = [{ + address: '' + }]; + $scope.bcc = [{ + address: '' + }]; $scope.subject = ''; $scope.body = ''; $scope.ciphertextPreview = ''; @@ -56,7 +62,7 @@ define(function(require) { $scope.writerTitle = 'Reply'; // fill recipient field - $scope.to = re.from[0].address; + $scope.to[0].address = re.from[0].address; // fill subject $scope.subject = 'Re: ' + ((re.subject) ? re.subject.replace('Re: ', '') : ''); @@ -75,18 +81,18 @@ define(function(require) { // Editing headers // - $scope.onAddressUpdate = function(recipient) { - var to = $scope.to, + $scope.onAddressUpdate = function(field, index) { + var recipient = field[index], address = recipient.address; // handle number of email inputs for multiple recipients if (address.indexOf(' ') !== -1) { recipient.address = address.replace(' ', ''); - to.push({ + field.push({ address: '' }); - } else if (address.length === 0 && to.length > 1) { - to.splice(to.indexOf(recipient), 1); + } else if (address.length === 0 && field.length > 1) { + field.splice(field.indexOf(recipient), 1); } verify(recipient); @@ -286,25 +292,20 @@ define(function(require) { }; }); - ngModule.directive('addressInput', function($timeout, $parse) { + ngModule.directive('addressInput', function($timeout) { return { //scope: true, // optionally create a child scope link: function(scope, element, attrs) { + // get prefix for id + var idPrefix = attrs.addressInput; element.bind('keydown', function(e) { if (e.keyCode === 32) { // space -> go to next input - e.preventDefault(); - element[0].parent[0].nextSibling[0].children[0].focus(); - } - scope.$apply(); - }); - - - var model = $parse(attrs.focusMe); - scope.$watch(model, function(value) { - if (value === true) { $timeout(function() { - element[0].focus(); + // find next input and focus + var index = attrs.id.replace(idPrefix, ''); + var nextId = idPrefix + (parseInt(index, 10) + 1); + document.getElementById(nextId).focus(); }, 100); } }); diff --git a/src/tpl/write.html b/src/tpl/write.html index 2ce5e1b..99e33bc 100644 --- a/src/tpl/write.html +++ b/src/tpl/write.html @@ -9,25 +9,27 @@

To: - - + -

Cc: - + + +

Bcc: - + + +

- +
-

+

-----BEGIN ENCRYPTED PREVIEW-----
{{ciphertextPreview}}
-----END ENCRYPTED PREVIEW-----

@@ -43,7 +45,7 @@
- +
From 87d26383f5e2433bf177264ecb14aefa7283a885 Mon Sep 17 00:00:00 2001 From: Tankred Hase Date: Mon, 13 Jan 2014 18:38:45 +0100 Subject: [PATCH 3/8] sending email to multiple receivers works --- src/js/controller/write.js | 50 ++++++++++++++++++++++++-------------- src/js/dao/email-dao.js | 46 +++++------------------------------ 2 files changed, 38 insertions(+), 58 deletions(-) diff --git a/src/js/controller/write.js b/src/js/controller/write.js index 56b9a3b..8818522 100644 --- a/src/js/controller/write.js +++ b/src/js/controller/write.js @@ -153,20 +153,13 @@ define(function(require) { }; $scope.sendToOutbox = function() { - var to, email; - - // validate recipients - to = $scope.to.replace(/\s/g, '').split(/[,;]/); - if (!to || to.length < 1) { - $scope.onError({ - errMsg: 'Seperate recipients with a comma!', - sync: true - }); - return; - } + var email; + // build email model for smtp-client email = { - to: [], // list of receivers + to: [], + cc: [], + bcc: [], subject: $scope.subject, // Subject line body: $scope.body // use parsed plaintext body }; @@ -174,13 +167,34 @@ define(function(require) { name: '', address: emailDao._account.emailAddress }]; - to.forEach(function(address) { - email.to.push({ - name: '', - address: address - }); - }); + // validate recipients and gather public keys + email.receiverKeys = []; // gather public keys for emailDao._encrypt + + appendReceivers($scope.to, email.to); + appendReceivers($scope.cc, email.cc); + appendReceivers($scope.bcc, email.bcc); + + function appendReceivers(srcField, destField) { + srcField.forEach(function(recipient) { + // validate address + if (!util.validateEmailAddress(recipient.address)) { + return; + } + + // append address to email model + destField.push({ + address: recipient.address + }); + + // add public key to list of recipient keys + if (recipient.key && recipient.key.publicKey) { + email.receiverKeys.push(recipient.key.publicKey); + } + }); + } + + // persist the email locally for later smtp transmission emailDao.store(email, function(err) { if (err) { $scope.onError(err); diff --git a/src/js/dao/email-dao.js b/src/js/dao/email-dao.js index 8352354..ebe818c 100644 --- a/src/js/dao/email-dao.js +++ b/src/js/dao/email-dao.js @@ -882,51 +882,17 @@ define(function(require) { return; } - // validate email addresses - for (var i = email.to.length - 1; i >= 0; i--) { - if (!util.validateEmailAddress(email.to[i].address)) { - callback({ - errMsg: 'Invalid recipient: ' + email.to[i].address - }); - return; - } - } - - if (!util.validateEmailAddress(email.from[0].address)) { - callback({ - errMsg: 'Invalid sender: ' + email.from - }); - return; - } - - // only support single recipient for e-2-e encryption - // check if receiver has a public key - self._keychain.getReceiverPublicKey(email.to[0].address, function(err, receiverPubkey) { + // public key found... encrypt and send + self._encrypt({ + email: email, + keys: email.receiverKeys // this Array is set in writer controller + }, function(err, email) { if (err) { callback(err); return; } - // validate public key - if (!receiverPubkey) { - callback({ - errMsg: 'User has no public key yet!' - }); - return; - } - - // public key found... encrypt and send - self._encrypt({ - email: email, - keys: [receiverPubkey.publicKey] - }, function(err, email) { - if (err) { - callback(err); - return; - } - - self._smtpClient.send(email, callback); - }); + self._smtpClient.send(email, callback); }); }; From 4b638a0deed3662c8d96b73ce03369b639ca2aa9 Mon Sep 17 00:00:00 2001 From: Tankred Hase Date: Mon, 13 Jan 2014 22:43:43 +0100 Subject: [PATCH 4/8] reading and writing to multiple recipients works --- src/js/controller/read.js | 2 ++ src/js/controller/write.js | 4 +++- src/tpl/read.html | 2 +- src/tpl/write.html | 6 ------ 4 files changed, 6 insertions(+), 8 deletions(-) diff --git a/src/js/controller/read.js b/src/js/controller/read.js index b93d21b..90aaac8 100644 --- a/src/js/controller/read.js +++ b/src/js/controller/read.js @@ -53,6 +53,8 @@ define(function(require) { mail.from.forEach(checkPublicKey); // display recipient security status mail.to.forEach(checkPublicKey); + // display recipient security status + Array.isArray(mail.cc) && mail.cc.forEach(checkPublicKey); }); function checkPublicKey(user) { diff --git a/src/js/controller/write.js b/src/js/controller/write.js index 8818522..691afc4 100644 --- a/src/js/controller/write.js +++ b/src/js/controller/write.js @@ -62,7 +62,9 @@ define(function(require) { $scope.writerTitle = 'Reply'; // fill recipient field - $scope.to[0].address = re.from[0].address; + $scope.to.unshift({ + address: re.from[0].address + }); // fill subject $scope.subject = 'Re: ' + ((re.subject) ? re.subject.replace('Re: ', '') : ''); diff --git a/src/tpl/read.html b/src/tpl/read.html index bcaefe1..ca2dbf9 100644 --- a/src/tpl/read.html +++ b/src/tpl/read.html @@ -16,7 +16,7 @@

- CC: {{u.name || u.address}} + Cc: {{u.name || u.address}}

diff --git a/src/tpl/write.html b/src/tpl/write.html index 99e33bc..9f76f5f 100644 --- a/src/tpl/write.html +++ b/src/tpl/write.html @@ -19,12 +19,6 @@

-

- Bcc: - - - -

From 2eaf7ca17230bb3d9fbc12461827edd1ed7de7b2 Mon Sep 17 00:00:00 2001 From: Tankred Hase Date: Mon, 13 Jan 2014 23:54:53 +0100 Subject: [PATCH 5/8] check valid send states in editor --- src/js/controller/write.js | 44 +++++++++++++++++++++++++++++++------- src/sass/views/_read.scss | 2 +- src/sass/views/_write.scss | 6 ++++++ src/tpl/write.html | 2 +- 4 files changed, 44 insertions(+), 10 deletions(-) diff --git a/src/js/controller/write.js b/src/js/controller/write.js index 691afc4..96236e9 100644 --- a/src/js/controller/write.js +++ b/src/js/controller/write.js @@ -108,6 +108,7 @@ define(function(require) { // verify email address if (!util.validateEmailAddress(recipient.address)) { recipient.secure = undefined; + checkSendStatus(); return; } @@ -122,21 +123,48 @@ define(function(require) { if (key && key.userId === recipient.address) { recipient.key = key; recipient.secure = true; - $scope.$apply(); } + + checkSendStatus(); + $scope.$apply(); }); } - function resetDisplay() { + function checkSendStatus() { + $scope.okToSend = false; $scope.sendBtnText = undefined; - } + $scope.sendBtnSecure = undefined; - function displaySecure() { - $scope.sendBtnText = 'Send securely'; - } + var allSecure = true; + var numReceivers = 0; - function displayInsecure() { - $scope.sendBtnText = 'Invite & send securely'; + // count number of receivers and check security + $scope.to.forEach(check); + $scope.cc.forEach(check); + $scope.bcc.forEach(check); + + function check(recipient) { + // validate address + if (!util.validateEmailAddress(recipient.address)) { + return; + } + numReceivers++; + if (!recipient.secure) { + allSecure = false; + } + } + + // sender can invite only one use at a time + if (!allSecure && numReceivers === 1) { + $scope.sendBtnText = 'Invite & send securely'; + $scope.okToSend = true; + $scope.sendBtnSecure = false; + } else if (allSecure && numReceivers > 0) { + // all recipients are secure + $scope.sendBtnText = 'Send securely'; + $scope.okToSend = true; + $scope.sendBtnSecure = true; + } } // diff --git a/src/sass/views/_read.scss b/src/sass/views/_read.scss index 00f6ef7..535488b 100644 --- a/src/sass/views/_read.scss +++ b/src/sass/views/_read.scss @@ -7,7 +7,7 @@ .headers { p { margin: 0px; - padding: 0px; + padding: 0px; } .subject { diff --git a/src/sass/views/_write.scss b/src/sass/views/_write.scss index a7c3780..bb309d4 100644 --- a/src/sass/views/_write.scss +++ b/src/sass/views/_write.scss @@ -20,6 +20,12 @@ .headers { margin-top: 10px; + + p { + margin: 0.2em 0; + padding: 0.2em 0; + } + span { color: $color-grey; } diff --git a/src/tpl/write.html b/src/tpl/write.html index 9f76f5f..c3ddf1c 100644 --- a/src/tpl/write.html +++ b/src/tpl/write.html @@ -39,7 +39,7 @@
- +
From 9bb70fac5daea2841cd808557ad4cd5ca9cce07e Mon Sep 17 00:00:00 2001 From: Tankred Hase Date: Tue, 14 Jan 2014 13:26:23 +0100 Subject: [PATCH 6/8] fix email dao tests --- src/js/dao/email-dao.js | 2 +- test/new-unit/email-dao-test.js | 66 ++------------------------------- 2 files changed, 4 insertions(+), 64 deletions(-) diff --git a/src/js/dao/email-dao.js b/src/js/dao/email-dao.js index ebe818c..66800c7 100644 --- a/src/js/dao/email-dao.js +++ b/src/js/dao/email-dao.js @@ -875,7 +875,7 @@ define(function(require) { } // validate the email input - if (!email.to || !email.from || !email.to[0].address || !email.from[0].address) { + if (!email.to || !email.from || !email.to[0].address || !email.from[0].address || !Array.isArray(email.receiverKeys)) { callback({ errMsg: 'Invalid email object!' }); diff --git a/test/new-unit/email-dao-test.js b/test/new-unit/email-dao-test.js index f596085..146183f 100644 --- a/test/new-unit/email-dao-test.js +++ b/test/new-unit/email-dao-test.js @@ -75,7 +75,8 @@ define(function(require) { subject: 'qweasd', body: 'asd', unread: false, - answered: false + answered: false, + receiverKeys: ['-----BEGIN PGP PUBLIC KEY-----\nasd\n-----END PGP PUBLIC KEY-----'] }; nonWhitelistedMail = { uid: 1234, @@ -2409,11 +2410,7 @@ define(function(require) { describe('sendEncrypted', function() { it('should work', function(done) { var encryptStub = sinon.stub(dao, '_encrypt').yields(null, {}); - keychainStub.getReceiverPublicKey.withArgs(dummyDecryptedMail.to[0].address).yields(null, { - _id: "fcf8b4aa-5d09-4089-8b4f-e3bc5091daf3", - userId: dummyDecryptedMail.to[0].address, - publicKey: publicKey - }); + smtpClientStub.send.yields(); dao.sendEncrypted({ @@ -2421,7 +2418,6 @@ define(function(require) { }, function(err) { expect(err).to.not.exist; - expect(keychainStub.getReceiverPublicKey.calledOnce).to.be.true; expect(encryptStub.calledOnce).to.be.true; expect(smtpClientStub.send.calledOnce).to.be.true; @@ -2430,72 +2426,18 @@ define(function(require) { }); it('should not work when encryption fails', function(done) { var encryptStub = sinon.stub(dao, '_encrypt').yields({}); - keychainStub.getReceiverPublicKey.withArgs(dummyDecryptedMail.to[0].address).yields(null, { - _id: "fcf8b4aa-5d09-4089-8b4f-e3bc5091daf3", - userId: dummyDecryptedMail.to[0].address, - publicKey: publicKey - }); dao.sendEncrypted({ email: dummyDecryptedMail }, function(err) { expect(err).to.exist; - expect(keychainStub.getReceiverPublicKey.calledOnce).to.be.true; expect(encryptStub.calledOnce).to.be.true; expect(smtpClientStub.send.called).to.be.false; done(); }); }); - it('should not work when key retrieval fails', function(done) { - var encryptStub = sinon.stub(dao, '_encrypt'); - keychainStub.getReceiverPublicKey.withArgs(dummyDecryptedMail.to[0].address).yields({}); - - dao.sendEncrypted({ - email: dummyDecryptedMail - }, function(err) { - expect(err).to.exist; - - expect(keychainStub.getReceiverPublicKey.calledOnce).to.be.true; - expect(encryptStub.called).to.be.false; - expect(smtpClientStub.send.called).to.be.false; - - done(); - }); - }); - it('should not work invalid recipients', function(done) { - var encryptStub = sinon.stub(dao, '_encrypt'); - dummyDecryptedMail.to[0].address = 'asd@asd'; - - dao.sendEncrypted({ - email: dummyDecryptedMail - }, function(err) { - expect(err).to.exist; - - expect(keychainStub.getReceiverPublicKey.called).to.be.false; - expect(encryptStub.called).to.be.false; - expect(smtpClientStub.send.called).to.be.false; - - done(); - }); - }); - it('should not work with without sender', function(done) { - var encryptStub = sinon.stub(dao, '_encrypt'); - dummyDecryptedMail.from[0].address = 'asd@asd'; - - dao.sendEncrypted({ - email: dummyDecryptedMail - }, function(err) { - expect(err).to.exist; - - expect(keychainStub.getReceiverPublicKey.called).to.be.false; - expect(encryptStub.called).to.be.false; - expect(smtpClientStub.send.called).to.be.false; - - done(); - }); - }); it('should not work without recipients', function(done) { var encryptStub = sinon.stub(dao, '_encrypt'); delete dummyDecryptedMail.to; @@ -2505,7 +2447,6 @@ define(function(require) { }, function(err) { expect(err).to.exist; - expect(keychainStub.getReceiverPublicKey.called).to.be.false; expect(encryptStub.called).to.be.false; expect(smtpClientStub.send.called).to.be.false; @@ -2521,7 +2462,6 @@ define(function(require) { }, function(err) { expect(err).to.exist; - expect(keychainStub.getReceiverPublicKey.called).to.be.false; expect(encryptStub.called).to.be.false; expect(smtpClientStub.send.called).to.be.false; From 0dc1cc68bccccfb77a920626baf54c2cb82e5c8a Mon Sep 17 00:00:00 2001 From: Tankred Hase Date: Tue, 14 Jan 2014 16:11:59 +0100 Subject: [PATCH 7/8] fix tests --- src/js/controller/write.js | 25 ++- test/new-unit/write-ctrl-test.js | 280 ++++++++++++++++++++++++------- 2 files changed, 232 insertions(+), 73 deletions(-) diff --git a/src/js/controller/write.js b/src/js/controller/write.js index 96236e9..46e5e35 100644 --- a/src/js/controller/write.js +++ b/src/js/controller/write.js @@ -30,7 +30,7 @@ define(function(require) { fillFields(replyTo); $scope.updatePreview(); - verify($scope.to[0]); + $scope.verify($scope.to[0]); }, close: function() { this.open = false; @@ -83,6 +83,9 @@ define(function(require) { // Editing headers // + /** + * This event is fired when editing the email address headers. It checks is space is pressed and if so, creates a new address field. + */ $scope.onAddressUpdate = function(field, index) { var recipient = field[index], address = recipient.address; @@ -97,10 +100,13 @@ define(function(require) { field.splice(field.indexOf(recipient), 1); } - verify(recipient); + $scope.verify(recipient); }; - function verify(recipient) { + /** + * Verify and email address and fetch its public key + */ + $scope.verify = function(recipient) { // set display to insecure while fetching keys recipient.key = undefined; recipient.secure = false; @@ -108,7 +114,7 @@ define(function(require) { // verify email address if (!util.validateEmailAddress(recipient.address)) { recipient.secure = undefined; - checkSendStatus(); + $scope.checkSendStatus(); return; } @@ -125,12 +131,15 @@ define(function(require) { recipient.secure = true; } - checkSendStatus(); + $scope.checkSendStatus(); $scope.$apply(); }); - } + }; - function checkSendStatus() { + /** + * Check if it is ok to send an email depending on the invitation state of the addresses + */ + $scope.checkSendStatus = function() { $scope.okToSend = false; $scope.sendBtnText = undefined; $scope.sendBtnSecure = undefined; @@ -165,7 +174,7 @@ define(function(require) { $scope.okToSend = true; $scope.sendBtnSecure = true; } - } + }; // // Editing email body diff --git a/test/new-unit/write-ctrl-test.js b/test/new-unit/write-ctrl-test.js index 69e3b51..c4490bf 100644 --- a/test/new-unit/write-ctrl-test.js +++ b/test/new-unit/write-ctrl-test.js @@ -47,7 +47,9 @@ define(function(require) { expect(scope.state.writer.open).to.be.false; expect(scope.state.writer.write).to.exist; expect(scope.state.writer.close).to.exist; - expect(scope.verifyTo).to.exist; + expect(scope.verify).to.exist; + expect(scope.onAddressUpdate).to.exist; + expect(scope.checkSendStatus).to.exist; expect(scope.updatePreview).to.exist; expect(scope.sendToOutbox).to.exist; }); @@ -65,22 +67,24 @@ define(function(require) { describe('write', function() { it('should prepare write view', function() { - var verifyToMock = sinon.stub(scope, 'verifyTo'); + var verifyMock = sinon.stub(scope, 'verify'); scope.state.writer.write(); expect(scope.writerTitle).to.equal('New email'); - expect(scope.to).to.equal(''); + expect(scope.to).to.deep.equal([{ + address: '' + }]); expect(scope.subject).to.equal(''); expect(scope.body).to.equal(''); expect(scope.ciphertextPreview).to.equal(''); - expect(verifyToMock.calledOnce).to.be.true; + expect(verifyMock.calledOnce).to.be.true; - scope.verifyTo.restore(); + scope.verify.restore(); }); it('should prefill write view for response', function() { - var verifyToMock = sinon.stub(scope, 'verifyTo'), + var verifyMock = sinon.stub(scope, 'verify'), address = 'pity@dafool', subject = 'Ermahgerd!', body = 'so much body!', @@ -96,61 +100,210 @@ define(function(require) { scope.state.writer.write(re); expect(scope.writerTitle).to.equal('Reply'); - expect(scope.to).to.equal(address); + expect(scope.to).to.deep.equal([{ + address: address, + }, { + address: '' + }]); expect(scope.subject).to.equal('Re: ' + subject); expect(scope.body).to.contain(body); expect(scope.ciphertextPreview).to.not.be.empty; - expect(verifyToMock.calledOnce).to.be.true; + expect(verifyMock.calledOnce).to.be.true; - scope.verifyTo.restore(); + scope.verify.restore(); }); }); - describe('verifyTo', function() { - it('should verify the recipient as secure', function() { - var id = scope.to = 'pity@da.fool'; - keychainMock.getReceiverPublicKey.withArgs(id).yields(null, { - userId: id - }); + describe('onAddressUpdate', function() { + var verifyMock; - scope.verifyTo(); - - expect(scope.toSecure).to.be.true; - expect(scope.sendBtnText).to.equal('Send securely'); + beforeEach(function() { + verifyMock = sinon.stub(scope, 'verify'); }); - it('should verify the recipient as not secure', function(done) { - var id = scope.to = 'pity@da.fool'; - keychainMock.getReceiverPublicKey.withArgs(id).yields({ + afterEach(function() { + scope.verify.restore(); + }); + + it('should add new field item if space is pressed', function() { + var to = [{ + address: 'asdf@asdf.de ' + }]; + scope.onAddressUpdate(to, 0); + + expect(to.length).to.equal(2); + expect(to[0].address).to.equal('asdf@asdf.de'); + expect(to[1].address).to.equal(''); + expect(verifyMock.calledOnce).to.be.true; + }); + + it('should remove field item if address is empty', function() { + var to = [{ + address: 'asdf@asdf.de' + }, { + address: '' + }]; + scope.onAddressUpdate(to, 1); + + expect(to.length).to.equal(1); + expect(to[0].address).to.equal('asdf@asdf.de'); + expect(verifyMock.calledOnce).to.be.true; + }); + + it('should not remove last field item if address is empty', function() { + var to = [{ + address: '' + }]; + scope.onAddressUpdate(to, 0); + + expect(to.length).to.equal(1); + expect(to[0].address).to.equal(''); + expect(verifyMock.calledOnce).to.be.true; + }); + + it('should do nothing for normal address', function() { + var to = [{ + address: 'asdf@asdf.de' + }]; + scope.onAddressUpdate(to, 0); + + expect(to.length).to.equal(1); + expect(to[0].address).to.equal('asdf@asdf.de'); + expect(verifyMock.calledOnce).to.be.true; + }); + }); + + describe('verify', function() { + var checkSendStatusMock; + + beforeEach(function() { + checkSendStatusMock = sinon.stub(scope, 'checkSendStatus'); + }); + + afterEach(function() { + scope.checkSendStatus.restore(); + }); + + it('should not work for invalid email addresses', function() { + var recipient = { + address: '' + }; + + scope.verify(recipient); + + expect(recipient.key).to.be.undefined; + expect(recipient.secure).to.be.undefined; + expect(scope.checkSendStatus.calledOnce).to.be.true; + expect(keychainMock.getReceiverPublicKey.called).to.be.false; + }); + + it('should not work for error in keychain', function(done) { + var recipient = { + address: 'asds@example.com' + }; + + keychainMock.getReceiverPublicKey.withArgs(recipient.address).yields({ errMsg: '404 not found yadda yadda' }); scope.onError = function() { - expect(scope.toSecure).to.be.false; - expect(scope.sendBtnText).to.equal('Invite & send securely'); + expect(recipient.key).to.be.undefined; + expect(recipient.secure).to.be.false; + expect(scope.checkSendStatus.called).to.be.false; + expect(keychainMock.getReceiverPublicKey.calledOnce).to.be.true; done(); }; - scope.verifyTo(); + scope.verify(recipient); }); - it('should reset display if there is no recipient', function() { - scope.to = undefined; - scope.verifyTo(); + it('should work', function(done) { + var recipient = { + address: 'asdf@example.com' + }; + + keychainMock.getReceiverPublicKey.yields(null, { + userId: 'asdf@example.com' + }); + scope.$apply = function() { + expect(recipient.key).to.deep.equal({ + userId: 'asdf@example.com' + }); + expect(recipient.secure).to.be.true; + expect(scope.checkSendStatus.calledOnce).to.be.true; + expect(keychainMock.getReceiverPublicKey.calledOnce).to.be.true; + done(); + }; + + scope.verify(recipient); + }); + }); + + describe('checkSendStatus', function() { + beforeEach(function() { + scope.state.writer.write(); + }); + + afterEach(function() {}); + + it('should not be able to send with no recipients', function() { + scope.checkSendStatus(); + + expect(scope.okToSend).to.be.false; + expect(scope.sendBtnText).to.be.undefined; + expect(scope.sendBtnSecure).to.be.undefined; + }); + + it('should not be to invite 1 user', function() { + scope.to = [{ + address: 'asdf@asdf.de' + }]; + scope.checkSendStatus(); + + expect(scope.okToSend).to.be.true; + expect(scope.sendBtnText).to.equal('Invite & send securely'); + expect(scope.sendBtnSecure).to.be.false; + }); + + it('should not be able to invite multiple recipients', function() { + scope.to = [{ + address: 'asdf@asdf.de' + }, { + address: 'asdf@asdfg.de' + }]; + scope.checkSendStatus(); + + expect(scope.okToSend).to.be.false; + expect(scope.sendBtnText).to.be.undefined; + expect(scope.sendBtnSecure).to.be.undefined; + }); + + it('should be able to send securely to multiple recipients', function() { + scope.to = [{ + address: 'asdf@asdf.de', + secure: true + }, { + address: 'asdf@asdfg.de', + secure: true + }]; + scope.checkSendStatus(); + + expect(scope.okToSend).to.be.true; + expect(scope.sendBtnText).to.equal('Send securely'); + expect(scope.sendBtnSecure).to.be.true; }); }); describe('send to outbox', function() { it('should work when offline', function(done) { - var verifyToSpy = sinon.spy(scope, 'verifyTo'), - re = { - from: [{ - address: 'pity@dafool' - }], - subject: 'Ermahgerd!', - sentDate: new Date(), - body: 'so much body!' - }; + var re = { + from: [{ + address: 'pity@dafool' + }], + subject: 'Ermahgerd!', + sentDate: new Date(), + body: 'so much body!' + }; scope.state.nav = { currentFolder: 'currentFolder' @@ -160,11 +313,9 @@ define(function(require) { scope.onError = function(err) { expect(err).to.not.exist; expect(scope.state.writer.open).to.be.false; - expect(verifyToSpy.calledOnce).to.be.true; expect(emailDaoMock.store.calledOnce).to.be.true; expect(emailDaoMock.sync.calledOnce).to.be.true; - scope.verifyTo.restore(); done(); }; @@ -178,15 +329,14 @@ define(function(require) { }); it('should work', function(done) { - var verifyToSpy = sinon.spy(scope, 'verifyTo'), - re = { - from: [{ - address: 'pity@dafool' - }], - subject: 'Ermahgerd!', - sentDate: new Date(), - body: 'so much body!' - }; + var re = { + from: [{ + address: 'pity@dafool' + }], + subject: 'Ermahgerd!', + sentDate: new Date(), + body: 'so much body!' + }; scope.state.nav = { currentFolder: 'currentFolder' @@ -196,11 +346,9 @@ define(function(require) { scope.onError = function(err) { expect(err).to.not.exist; expect(scope.state.writer.open).to.be.false; - expect(verifyToSpy.calledOnce).to.be.true; expect(emailDaoMock.store.calledOnce).to.be.true; expect(emailDaoMock.sync.calledOnce).to.be.true; - scope.verifyTo.restore(); done(); }; @@ -212,15 +360,14 @@ define(function(require) { }); it('should fail', function(done) { - var verifyToSpy = sinon.spy(scope, 'verifyTo'), - re = { - from: [{ - address: 'pity@dafool' - }], - subject: 'Ermahgerd!', - sentDate: new Date(), - body: 'so much body!' - }; + var re = { + from: [{ + address: 'pity@dafool' + }], + subject: 'Ermahgerd!', + sentDate: new Date(), + body: 'so much body!' + }; scope.state.nav = { currentFolder: 'currentFolder' @@ -230,11 +377,9 @@ define(function(require) { scope.onError = function(err) { expect(err).to.exist; expect(scope.state.writer.open).to.be.false; - expect(verifyToSpy.calledOnce).to.be.true; expect(emailDaoMock.store.calledOnce).to.be.true; expect(emailDaoMock.sync.calledOnce).to.be.true; - scope.verifyTo.restore(); done(); }; @@ -246,14 +391,20 @@ define(function(require) { }); it('should not work and not close the write view', function(done) { - scope.state.writer.open = true; - scope.to = 'a, b, c'; + scope.state.writer.write(); + + scope.to = [{ + address: 'pity@dafool.de', + key: { + publicKey: '----- PGP Stuff -----' + } + }]; scope.body = 'asd'; scope.subject = 'yaddablabla'; scope.toKey = 'Public Key'; emailDaoMock.store.withArgs(sinon.match(function(mail) { - return mail.from[0].address === emailAddress && mail.to.length === 3; + return mail.from[0].address === emailAddress && mail.to.length === 1 && mail.receiverKeys.length === 1; })).yields({ errMsg: 'snafu' }); @@ -264,7 +415,6 @@ define(function(require) { expect(emailDaoMock.store.calledOnce).to.be.true; done(); }; - scope.sendToOutbox(); }); }); From 8dd30e3752cb1ce57642c3307002224acc3cbc14 Mon Sep 17 00:00:00 2001 From: Tankred Hase Date: Wed, 15 Jan 2014 10:57:28 +0100 Subject: [PATCH 8/8] externalize strings from editor --- src/js/app-config.js | 4 +++- src/js/controller/write.js | 5 +++-- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/src/js/app-config.js b/src/js/app-config.js index 7106b4e..026f38e 100644 --- a/src/js/app-config.js +++ b/src/js/app-config.js @@ -54,7 +54,9 @@ define(function(require) { cryptSuffix: '-----END PGP MESSAGE-----', signature: 'Sent securely from Whiteout Mail', webSite: 'http://whiteout.io', - verificationSubject: 'New public key uploaded' + verificationSubject: 'New public key uploaded', + sendBtnInvite: 'Invite & send securely', + sendBtnSecure: 'Send securely' }; return app; diff --git a/src/js/controller/write.js b/src/js/controller/write.js index 46e5e35..547228d 100644 --- a/src/js/controller/write.js +++ b/src/js/controller/write.js @@ -5,6 +5,7 @@ define(function(require) { appController = require('js/app-controller'), aes = require('cryptoLib/aes-cbc'), util = require('cryptoLib/util'), + str = require('js/app-config').string, emailDao; // @@ -165,12 +166,12 @@ define(function(require) { // sender can invite only one use at a time if (!allSecure && numReceivers === 1) { - $scope.sendBtnText = 'Invite & send securely'; + $scope.sendBtnText = str.sendBtnInvite; $scope.okToSend = true; $scope.sendBtnSecure = false; } else if (allSecure && numReceivers > 0) { // all recipients are secure - $scope.sendBtnText = 'Send securely'; + $scope.sendBtnText = str.sendBtnSecure; $scope.okToSend = true; $scope.sendBtnSecure = true; }