diff --git a/src/js/app-config.js b/src/js/app-config.js index 38be9a9..e6df9f4 100644 --- a/src/js/app-config.js +++ b/src/js/app-config.js @@ -60,13 +60,16 @@ define(function(require) { invitationSubject: 'Invitation to a private conversation', invitationMessage: 'Hi,\n\nI use Whiteout Mail to send and receive encrypted email. I would like to exchange encrypted messages with you as well.\n\nPlease install the Whiteout Mail application. This application makes it easy to read and write messages securely with PGP encryption applied.\n\nGo to the Whiteout Networks homepage to learn more and to download the application: https://whiteout.io\n\n', message: 'Hi,\n\nthis is a private conversation. To read my encrypted message below, simply open it in Whiteout Mail.\nOpen Whiteout Mail: https://chrome.google.com/webstore/detail/jjgghafhamholjigjoghcfcekhkonijg', - cryptPrefix: '-----BEGIN PGP MESSAGE-----', - cryptSuffix: '-----END PGP MESSAGE-----', signature: '\n\n\n--\nSent from Whiteout Mail - Email encryption for the rest of us\nhttps://whiteout.io\n\n', webSite: 'http://whiteout.io', verificationSubject: '[whiteout] New public key uploaded', sendBtnClear: 'Send', - sendBtnSecure: 'Send securely' + sendBtnSecure: 'Send securely', + updatePublicKeyTitle: 'Public Key Updated', + updatePublicKeyMsgNewKey: '{0} updated his key and may not be able to read encrypted messages sent with his old key. Update the key?', + updatePublicKeyMsgRemovedKey: '{0} revoked his key and may no longer be able to read encrypted messages. Remove the key?', + updatePublicKeyPosBtn: 'Yes', + updatePublicKeyNegBtn: 'No' }; return app; diff --git a/src/js/app-controller.js b/src/js/app-controller.js index 0ad3a41..b799ee4 100644 --- a/src/js/app-controller.js +++ b/src/js/app-controller.js @@ -14,7 +14,9 @@ define(function(require) { ImapClient = require('imap-client'), RestDAO = require('js/dao/rest-dao'), EmailDAO = require('js/dao/email-dao'), - config = require('js/app-config').config, + appConfig = require('js/app-config'), + config = appConfig.config, + str = appConfig.string, KeychainDAO = require('js/dao/keychain-dao'), PublicKeyDAO = require('js/dao/publickey-dao'), LawnchairDAO = require('js/dao/lawnchair-dao'), @@ -64,11 +66,25 @@ define(function(require) { pubkeyDao = new PublicKeyDAO(restDao); oauth = new OAuth(new RestDAO('https://www.googleapis.com')); + self._keychain = keychain = new KeychainDAO(lawnchairDao, pubkeyDao); + keychain.requestPermissionForKeyUpdate = function(params, callback) { + var message = params.newKey ? str.updatePublicKeyMsgNewKey : str.updatePublicKeyMsgRemovedKey; + message = message.replace('{0}', params.userId); + + options.onError({ + title: str.updatePublicKeyTitle, + message: message, + positiveBtnStr: str.updatePublicKeyPosBtn, + negativeBtnStr: str.updatePublicKeyNegBtn, + showNegativeBtn: true, + callback: callback + }); + }; + self._appConfigStore = appConfigStore = new DeviceStorageDAO(new LawnchairDAO()); self._auth = new Auth(appConfigStore, oauth, new RestDAO('/ca')); self._userStorage = userStorage = new DeviceStorageDAO(lawnchairDao); self._invitationDao = new InvitationDAO(restDao); - self._keychain = keychain = new KeychainDAO(lawnchairDao, pubkeyDao); self._crypto = pgp = new PGP(); self._pgpbuilder = pgpbuilder = new PgpBuilder(); self._emailDao = emailDao = new EmailDAO(keychain, pgp, userStorage, pgpbuilder, mailreader); diff --git a/src/js/controller/mail-list.js b/src/js/controller/mail-list.js index e5e3a0e..0759cae 100644 --- a/src/js/controller/mail-list.js +++ b/src/js/controller/mail-list.js @@ -6,7 +6,7 @@ define(function(require) { appController = require('js/app-controller'), IScroll = require('iscroll'), notification = require('js/util/notification'), - emailDao, outboxBo; + emailDao, outboxBo, keychainDao; var MailListCtrl = function($scope, $timeout) { // @@ -15,6 +15,7 @@ define(function(require) { emailDao = appController._emailDao; outboxBo = appController._outboxBo; + keychainDao = appController._keychain; // // scope functions @@ -55,17 +56,25 @@ define(function(require) { $scope.state.mailList.selected = email; $scope.state.read.toggle(true); - emailDao.decryptBody({ - message: email - }, $scope.onError); + keychainDao.refreshKeyForUserId(email.from[0].address, onKeyRefreshed); - // if the email is unread, please sync the new state. - // otherweise forget about it. - if (!email.unread) { - return; + function onKeyRefreshed(err) { + if (err) { + $scope.onError(err); + } + + emailDao.decryptBody({ + message: email + }, $scope.onError); + + // if the email is unread, please sync the new state. + // otherweise forget about it. + if (!email.unread) { + return; + } + + $scope.toggleUnread(email); } - - $scope.toggleUnread(email); }; /** diff --git a/src/js/controller/write.js b/src/js/controller/write.js index ed96ef8..845289a 100644 --- a/src/js/controller/write.js +++ b/src/js/controller/write.js @@ -7,7 +7,7 @@ define(function(require) { aes = require('cryptoLib/aes-cbc'), util = require('cryptoLib/util'), str = require('js/app-config').string, - crypto, emailDao, outbox; + crypto, emailDao, outbox, keychainDao; // // Controller @@ -17,6 +17,8 @@ define(function(require) { crypto = appController._crypto; emailDao = appController._emailDao, outbox = appController._outboxBo; + keychainDao = appController._keychain; + // set default value so that the popover height is correct on init $scope.keyId = 'XXXXXXXX'; @@ -185,7 +187,8 @@ define(function(require) { } // check if to address is contained in known public keys - emailDao._keychain.getReceiverPublicKey(recipient.address, function(err, key) { + // when we write an email, we always need to work with the latest keys available + keychainDao.refreshKeyForUserId(recipient.address, function(err, key) { if (err) { $scope.onError(err); return; diff --git a/src/js/dao/keychain-dao.js b/src/js/dao/keychain-dao.js index 7669460..2ee4e69 100644 --- a/src/js/dao/keychain-dao.js +++ b/src/js/dao/keychain-dao.js @@ -66,6 +66,96 @@ define(function(require) { }); }; + /** + * Checks for public key updates of a given user id + * @param {String} userId The user id (email address) for which to check the key + * @param {Function} callback(error, key) Invoked when the key has been updated or an error occurred + */ + KeychainDAO.prototype.refreshKeyForUserId = function(userId, callback) { + var self = this; + + // get the public key corresponding to the userId + self.getReceiverPublicKey(userId, function(err, localKey) { + if (!localKey || !localKey._id) { + // there is no key available, no need to refresh + callback(); + return; + } + + // check if the key id still exists on the key server + checkKeyExists(localKey); + }); + + // checks if the user's key has been revoked by looking up the key id + function checkKeyExists(localKey) { + self._publicKeyDao.get(localKey._id, function(err, cloudKey) { + if (err && err.code === 42) { + // we're offline, we're done checking the key + callback(null, localKey); + return; + } + + if (err) { + // there was an error, exit and inform + callback(err); + return; + } + + if (cloudKey && cloudKey._id === localKey._id) { + // the key is present on the server, all is well + callback(null, localKey); + return; + } + + // the key has changed, update the key + updateKey(localKey); + }); + } + + function updateKey(localKey) { + // look for an updated key for the user id + self._publicKeyDao.getByUserId(userId, function(err, newKey) { + // offline? + if (err && err.code === 42) { + callback(null, localKey); + return; + } + + if (err) { + callback(err); + return; + } + + // the public key has changed, we need to ask for permission to update the key + self.requestPermissionForKeyUpdate({ + userId: userId, + newKey: newKey + }, function(granted) { + if (!granted) { + // permission was not given to update the key, so don't overwrite the old one! + callback(null, localKey); + return; + } + + // permission to update the key was given, so delete the old one and persist the new one + self.removeLocalPublicKey(localKey._id, function(err) { + if (err || !newKey) { + // error or no new key to save + callback(err); + return; + } + + // persist the new key and return it + self.saveLocalPublicKey(newKey, function(err) { + callback(err, err ? undefined : newKey); + }); + }); + }); + + }); + } + }; + /** * Look up a reveiver's public key by user id * @param userId [String] the receiver's email address @@ -92,23 +182,27 @@ define(function(require) { // no public key by that user id in storage // find from cloud by email address - self._publicKeyDao.getByUserId(userId, onKeyGotten); + self._publicKeyDao.getByUserId(userId, onKeyReceived); }); - function onKeyGotten(err, cloudPubkey) { + function onKeyReceived(err, cloudPubkey) { + if (err && err.code === 42) { + // offline + callback(); + return; + } + if (err) { callback(err); return; } if (!cloudPubkey) { - // no public key for that user + // public key has been deleted without replacement callback(); return; } - // there is a public key for that user already in the cloud... - // save to local storage self.saveLocalPublicKey(cloudPubkey, function(err) { if (err) { callback(err); diff --git a/src/js/dao/publickey-dao.js b/src/js/dao/publickey-dao.js index 0674d76..a2ebba0 100644 --- a/src/js/dao/publickey-dao.js +++ b/src/js/dao/publickey-dao.js @@ -34,19 +34,17 @@ define(function() { this._restDao.get({ uri: uri }, function(err, key) { + if (err && err.code === 404) { + callback(); + return; + } + if (err) { callback(err); return; } - if (!key || !key._id) { - callback({ - errMsg: 'No public key for that user!' - }); - return; - } - - callback(null, key); + callback(null, (key && key._id) ? key : undefined); }); }; diff --git a/src/js/dao/rest-dao.js b/src/js/dao/rest-dao.js index 581747d..78f4236 100644 --- a/src/js/dao/rest-dao.js +++ b/src/js/dao/rest-dao.js @@ -68,7 +68,7 @@ define(function(require) { xhr.onerror = function() { callback({ - code: 404, + code: 42, errMsg: 'Error calling GET on ' + options.uri }); }; diff --git a/src/js/util/error.js b/src/js/util/error.js index ba9b8bd..de4b80e 100644 --- a/src/js/util/error.js +++ b/src/js/util/error.js @@ -2,7 +2,6 @@ define(function() { 'use strict'; var er = {}; - er.attachHandler = function(scope) { scope.onError = function(options) { if (!options) { @@ -19,7 +18,11 @@ define(function() { scope.state.dialog = { open: true, title: options.title || 'Error', - message: options.errMsg || options.message + message: options.errMsg || options.message, + positiveBtnStr: options.positiveBtnStr || 'Ok', + negativeBtnStr: options.negativeBtnStr || 'Cancel', + showNegativeBtn: options.showNegativeBtn || false, + callback: options.callback }; // don't call apply for synchronous calls if (!options.sync) { diff --git a/src/tpl/dialog.html b/src/tpl/dialog.html index cc24300..ae98177 100644 --- a/src/tpl/dialog.html +++ b/src/tpl/dialog.html @@ -7,7 +7,8 @@

{{state.dialog.message}}

- + +
\ No newline at end of file diff --git a/test/new-unit/keychain-dao-test.js b/test/new-unit/keychain-dao-test.js index e55bacc..153cff4 100644 --- a/test/new-unit/keychain-dao-test.js +++ b/test/new-unit/keychain-dao-test.js @@ -56,6 +56,255 @@ define(function(require) { }); }); + describe('refreshKeyForUserId', function() { + var getPubKeyStub, + oldKey = { + _id: 123 + }, newKey = { + _id: 456 + }; + + beforeEach(function() { + getPubKeyStub = sinon.stub(keychainDao, 'getReceiverPublicKey'); + }); + + afterEach(function() { + keychainDao.getReceiverPublicKey.restore(); + delete keychainDao.requestPermissionForKeyUpdate; + }); + + it('should not find a key', function(done) { + getPubKeyStub.yields(); + + keychainDao.refreshKeyForUserId(testUser, function(err, key) { + expect(err).to.not.exist; + expect(key).to.not.exist; + + done(); + }); + }); + + it('should not update the key when up to date', function(done) { + getPubKeyStub.yields(null, oldKey); + pubkeyDaoStub.get.withArgs(oldKey._id).yields(null, oldKey); + + keychainDao.refreshKeyForUserId(testUser, function(err, key) { + expect(err).to.not.exist; + expect(key).to.to.equal(oldKey); + + expect(getPubKeyStub.calledOnce).to.be.true; + expect(pubkeyDaoStub.get.calledOnce).to.be.true; + + + done(); + }); + }); + + it('should update key', function(done) { + getPubKeyStub.yields(null, oldKey); + pubkeyDaoStub.get.withArgs(oldKey._id).yields(); + pubkeyDaoStub.getByUserId.withArgs(testUser).yields(null, newKey); + keychainDao.requestPermissionForKeyUpdate = function(opts, cb) { + expect(opts.userId).to.equal(testUser); + expect(opts.newKey).to.equal(newKey); + cb(true); + }; + lawnchairDaoStub.remove.withArgs('publickey_' + oldKey._id).yields(); + lawnchairDaoStub.persist.withArgs('publickey_' + newKey._id, newKey).yields(); + + keychainDao.refreshKeyForUserId(testUser, function(err, key) { + expect(err).to.not.exist; + expect(key).to.equal(newKey); + + expect(getPubKeyStub.calledOnce).to.be.true; + expect(pubkeyDaoStub.get.calledOnce).to.be.true; + expect(pubkeyDaoStub.getByUserId.calledOnce).to.be.true; + expect(lawnchairDaoStub.remove.calledOnce).to.be.true; + expect(lawnchairDaoStub.persist.calledOnce).to.be.true; + + done(); + }); + }); + + it('should remove key', function(done) { + getPubKeyStub.yields(null, oldKey); + pubkeyDaoStub.get.withArgs(oldKey._id).yields(); + pubkeyDaoStub.getByUserId.withArgs(testUser).yields(); + keychainDao.requestPermissionForKeyUpdate = function(opts, cb) { + expect(opts.userId).to.equal(testUser); + expect(opts.newKey).to.not.exist; + cb(true); + }; + lawnchairDaoStub.remove.withArgs('publickey_' + oldKey._id).yields(); + + keychainDao.refreshKeyForUserId(testUser, function(err, key) { + expect(err).to.not.exist; + expect(key).to.not.exist; + + expect(getPubKeyStub.calledOnce).to.be.true; + expect(pubkeyDaoStub.get.calledOnce).to.be.true; + expect(pubkeyDaoStub.getByUserId.calledOnce).to.be.true; + expect(lawnchairDaoStub.remove.calledOnce).to.be.true; + expect(lawnchairDaoStub.persist.called).to.be.false; + + done(); + }); + }); + + it('should go offline while fetching new key', function(done) { + getPubKeyStub.yields(null, oldKey); + pubkeyDaoStub.get.withArgs(oldKey._id).yields(); + pubkeyDaoStub.getByUserId.withArgs(testUser).yields({ + code: 42 + }); + + keychainDao.refreshKeyForUserId(testUser, function(err, key) { + expect(err).to.not.exist; + expect(key).to.to.equal(oldKey); + + expect(getPubKeyStub.calledOnce).to.be.true; + expect(pubkeyDaoStub.get.calledOnce).to.be.true; + expect(pubkeyDaoStub.getByUserId.calledOnce).to.be.true; + expect(lawnchairDaoStub.remove.called).to.be.false; + expect(lawnchairDaoStub.persist.called).to.be.false; + + done(); + }); + }); + + it('should not remove old key on user rejection', function(done) { + getPubKeyStub.yields(null, oldKey); + pubkeyDaoStub.get.withArgs(oldKey._id).yields(); + pubkeyDaoStub.getByUserId.withArgs(testUser).yields(null, newKey); + keychainDao.requestPermissionForKeyUpdate = function(opts, cb) { + expect(opts.userId).to.equal(testUser); + expect(opts.newKey).to.exist; + cb(false); + }; + + keychainDao.refreshKeyForUserId(testUser, function(err, key) { + expect(err).to.not.exist; + expect(key).to.equal(oldKey); + + expect(getPubKeyStub.calledOnce).to.be.true; + expect(pubkeyDaoStub.get.calledOnce).to.be.true; + expect(pubkeyDaoStub.getByUserId.calledOnce).to.be.true; + expect(lawnchairDaoStub.remove.called).to.be.false; + expect(lawnchairDaoStub.persist.called).to.be.false; + + done(); + }); + }); + + it('should update not the key when offline', function(done) { + getPubKeyStub.yields(null, oldKey); + pubkeyDaoStub.get.withArgs(oldKey._id).yields({ + code: 42 + }); + + keychainDao.refreshKeyForUserId(testUser, function(err, key) { + expect(err).to.not.exist; + expect(key).to.to.equal(oldKey); + + expect(getPubKeyStub.calledOnce).to.be.true; + expect(pubkeyDaoStub.get.calledOnce).to.be.true; + expect(pubkeyDaoStub.getByUserId.called).to.be.false; + expect(lawnchairDaoStub.remove.called).to.be.false; + expect(lawnchairDaoStub.persist.called).to.be.false; + + done(); + }); + }); + + it('should error while persisting new key', function(done) { + getPubKeyStub.yields(null, oldKey); + pubkeyDaoStub.get.withArgs(oldKey._id).yields(); + pubkeyDaoStub.getByUserId.withArgs(testUser).yields(null, newKey); + keychainDao.requestPermissionForKeyUpdate = function(opts, cb) { + expect(opts.userId).to.equal(testUser); + expect(opts.newKey).to.equal(newKey); + cb(true); + }; + lawnchairDaoStub.remove.withArgs('publickey_' + oldKey._id).yields(); + lawnchairDaoStub.persist.yields({}); + + keychainDao.refreshKeyForUserId(testUser, function(err, key) { + expect(err).to.exist; + expect(key).to.not.exist; + + expect(getPubKeyStub.calledOnce).to.be.true; + expect(pubkeyDaoStub.get.calledOnce).to.be.true; + expect(pubkeyDaoStub.getByUserId.calledOnce).to.be.true; + expect(lawnchairDaoStub.remove.calledOnce).to.be.true; + expect(lawnchairDaoStub.persist.calledOnce).to.be.true; + + done(); + }); + }); + + it('should error while deleting old key', function(done) { + getPubKeyStub.yields(null, oldKey); + pubkeyDaoStub.get.withArgs(oldKey._id).yields(); + pubkeyDaoStub.getByUserId.withArgs(testUser).yields(); + keychainDao.requestPermissionForKeyUpdate = function(opts, cb) { + expect(opts.userId).to.equal(testUser); + cb(true); + }; + lawnchairDaoStub.remove.yields({}); + + keychainDao.refreshKeyForUserId(testUser, function(err, key) { + expect(err).to.exist; + expect(key).to.not.exist; + + expect(getPubKeyStub.calledOnce).to.be.true; + expect(pubkeyDaoStub.get.calledOnce).to.be.true; + expect(lawnchairDaoStub.remove.calledOnce).to.be.true; + expect(pubkeyDaoStub.getByUserId.calledOnce).to.be.true; + expect(lawnchairDaoStub.persist.called).to.be.false; + + done(); + }); + }); + + it('should error while persisting new key', function(done) { + getPubKeyStub.yields(null, oldKey); + pubkeyDaoStub.get.withArgs(oldKey._id).yields(); + pubkeyDaoStub.getByUserId.withArgs(testUser).yields(null, newKey); + keychainDao.requestPermissionForKeyUpdate = function(opts, cb) { + expect(opts.userId).to.equal(testUser); + expect(opts.newKey).to.equal(newKey); + cb(true); + }; + lawnchairDaoStub.remove.withArgs('publickey_' + oldKey._id).yields(); + lawnchairDaoStub.persist.yields({}); + + keychainDao.refreshKeyForUserId(testUser, function(err, key) { + expect(err).to.exist; + expect(key).to.not.exist; + + expect(getPubKeyStub.calledOnce).to.be.true; + expect(pubkeyDaoStub.get.calledOnce).to.be.true; + expect(pubkeyDaoStub.getByUserId.calledOnce).to.be.true; + expect(lawnchairDaoStub.remove.calledOnce).to.be.true; + expect(lawnchairDaoStub.persist.calledOnce).to.be.true; + + done(); + }); + }); + + it('should error when get failed', function(done) { + getPubKeyStub.yields(null, oldKey); + pubkeyDaoStub.get.withArgs(oldKey._id).yields({}); + + keychainDao.refreshKeyForUserId(testUser, function(err, key) { + expect(err).to.exist; + expect(key).to.not.exist; + + done(); + }); + }); + }); + describe('lookup public key', function() { it('should fail', function(done) { keychainDao.lookupPublicKey(undefined, function(err, key) { @@ -182,7 +431,7 @@ define(function(require) { it('should fail due to error in pubkey dao', function(done) { lawnchairDaoStub.list.yields(); - pubkeyDaoStub.getByUserId.yields(42); + pubkeyDaoStub.getByUserId.yields({}); keychainDao.getReceiverPublicKey(testUser, function(err, key) { expect(err).to.exist; diff --git a/test/new-unit/mail-list-ctrl-test.js b/test/new-unit/mail-list-ctrl-test.js index e1d4bcf..1c94679 100644 --- a/test/new-unit/mail-list-ctrl-test.js +++ b/test/new-unit/mail-list-ctrl-test.js @@ -19,9 +19,9 @@ define(function(require) { hasChrome, hasSocket, hasRuntime, hasIdentity; beforeEach(function() { - hasChrome = !! window.chrome; - hasSocket = !! window.chrome.socket; - hasIdentity = !! window.chrome.identity; + hasChrome = !!window.chrome; + hasSocket = !!window.chrome.socket; + hasIdentity = !!window.chrome.identity; if (!hasChrome) { window.chrome = {}; } @@ -62,7 +62,7 @@ define(function(require) { keychainMock = sinon.createStubInstance(KeychainDAO); - emailDaoMock._keychain = keychainMock; + appController._keychain = keychainMock; deviceStorageMock = sinon.createStubInstance(DeviceStorageDAO); emailDaoMock._devicestorage = deviceStorageMock; @@ -224,9 +224,12 @@ define(function(require) { }); describe('select', function() { - it('should decrypt, focus, and mark an unread mail as read', function() { + it('should decrypt, focus mark an unread mail as read', function() { var mail = { - unread: true + from: [{ + address: 'asd' + }], + unread: true, }; scope.state = { nav: { @@ -240,16 +243,23 @@ define(function(require) { } }; + keychainMock.refreshKeyForUserId.withArgs(mail.from[0].address).yields(); + scope.select(mail); expect(emailDaoMock.decryptBody.calledOnce).to.be.true; + expect(keychainMock.refreshKeyForUserId.calledOnce).to.be.true; expect(scope.state.mailList.selected).to.equal(mail); }); it('should decrypt and focus a read mail', function() { var mail = { + from: [{ + address: 'asd' + }], unread: false }; + scope.state = { mailList: {}, read: { @@ -262,9 +272,12 @@ define(function(require) { } }; + keychainMock.refreshKeyForUserId.withArgs(mail.from[0].address).yields(); + scope.select(mail); expect(emailDaoMock.decryptBody.calledOnce).to.be.true; + expect(keychainMock.refreshKeyForUserId.calledOnce).to.be.true; expect(scope.state.mailList.selected).to.equal(mail); }); }); diff --git a/test/new-unit/write-ctrl-test.js b/test/new-unit/write-ctrl-test.js index 43aed7e..78e9f08 100644 --- a/test/new-unit/write-ctrl-test.js +++ b/test/new-unit/write-ctrl-test.js @@ -12,7 +12,7 @@ define(function(require) { describe('Write controller unit test', function() { var ctrl, scope, - origEmailDao, origOutbox, + origEmailDao, origOutbox, origKeychain, emailDaoMock, keychainMock, outboxMock, emailAddress; beforeEach(function() { @@ -20,6 +20,7 @@ define(function(require) { // outbox and email dao to restore it after the tests origEmailDao = appController._emailDao; origOutbox = appController._outboxBo; + origKeychain = appController._keychain; outboxMock = sinon.createStubInstance(OutboxBO); appController._outboxBo = outboxMock; @@ -33,7 +34,7 @@ define(function(require) { }; keychainMock = sinon.createStubInstance(KeychainDAO); - emailDaoMock._keychain = keychainMock; + appController._keychain = keychainMock; angular.module('writetest', []); mocks.module('writetest'); @@ -50,6 +51,7 @@ define(function(require) { // restore the app controller appController._emailDao = origEmailDao; appController._outboxBo = origOutbox; + appController._keychain = origKeychain; }); describe('scope variables', function() { @@ -215,14 +217,15 @@ define(function(require) { address: 'asds@example.com' }; - keychainMock.getReceiverPublicKey.withArgs(recipient.address).yields({ + keychainMock.refreshKeyForUserId.withArgs(recipient.address).yields({ errMsg: '404 not found yadda yadda' }); + scope.onError = function() { expect(recipient.key).to.be.undefined; expect(recipient.secure).to.be.false; expect(scope.checkSendStatus.callCount).to.equal(1); - expect(keychainMock.getReceiverPublicKey.calledOnce).to.be.true; + expect(keychainMock.refreshKeyForUserId.calledOnce).to.be.true; done(); }; @@ -234,16 +237,17 @@ define(function(require) { address: 'asdf@example.com' }; - keychainMock.getReceiverPublicKey.yields(null, { + keychainMock.refreshKeyForUserId.withArgs(recipient.address).yields(null, { userId: 'asdf@example.com' }); + scope.$digest = function() { expect(recipient.key).to.deep.equal({ userId: 'asdf@example.com' }); expect(recipient.secure).to.be.true; expect(scope.checkSendStatus.callCount).to.equal(2); - expect(keychainMock.getReceiverPublicKey.calledOnce).to.be.true; + expect(keychainMock.refreshKeyForUserId.calledOnce).to.be.true; done(); };