From 553320adc860fc7584ed7b0af92c180f3cea497d Mon Sep 17 00:00:00 2001 From: Tankred Hase Date: Fri, 16 Aug 2013 20:50:47 +0200 Subject: [PATCH] refactor email dao for prototype style and also use spaces instead of tabs --- src/js/dao/email-dao.js | 590 ++++++++++++++++++++-------------------- 1 file changed, 301 insertions(+), 289 deletions(-) diff --git a/src/js/dao/email-dao.js b/src/js/dao/email-dao.js index ea7b072..2664eca 100644 --- a/src/js/dao/email-dao.js +++ b/src/js/dao/email-dao.js @@ -3,336 +3,348 @@ * between the cloud service and the device's local storage */ define(['underscore', 'cryptoLib/util', 'js/crypto/crypto', 'js/dao/lawnchair-dao', - 'js/dao/devicestorage-dao', 'js/app-config', 'js/model/account-model' + 'js/dao/devicestorage-dao', 'js/app-config', 'js/model/account-model' ], function(_, util, crypto, jsonDB, devicestorage, app) { - 'use strict'; + 'use strict'; - var EmailDAO = function(cloudstorage, keychain, imapClient, smtpClient) { - var self = this; + var EmailDAO = function(cloudstorage, keychain, imapClient, smtpClient) { + var self = this; - /** - * Inits all dependencies - */ - self.init = function(account, password, callback) { - self.account = account; + self._cloudstorage = cloudstorage; + self._keychain = keychain; + self._imapClient = imapClient; + self._smtpClient = smtpClient; + }; - // validate email address - var emailAddress = account.get('emailAddress'); - if (!validateEmail(emailAddress)) { - callback({ - errMsg: 'The user email address must be specified!' - }); - return; - } + /** + * Inits all dependencies + */ + EmailDAO.prototype.init = function(account, password, callback) { + var self = this; - // login IMAP client if existent - if (imapClient) { - imapClient.login(function() { - console.log('logged into imap.'); - initKeychain(); - }); - } else { - initKeychain(); - } + self.account = account; - function initKeychain() { - // init user's local database - jsonDB.init(emailAddress); + // validate email address + var emailAddress = account.get('emailAddress'); + if (!validateEmail(emailAddress)) { + callback({ + errMsg: 'The user email address must be specified!' + }); + return; + } - // call getUserKeyPair to read/sync keypair with devicestorage/cloud - keychain.getUserKeyPair(emailAddress, function(err, storedKeypair) { - if (err) { - callback(err); - return; - } - // init crypto - initCrypto(storedKeypair); - }); - } + // login IMAP client if existent + if (self._imapClient) { + self._imapClient.login(function() { + console.log('logged into imap.'); + initKeychain(); + }); + } else { + initKeychain(); + } - function initCrypto(storedKeypair) { - crypto.init({ - emailAddress: emailAddress, - password: password, - keySize: account.get('symKeySize'), - rsaKeySize: account.get('asymKeySize'), - storedKeypair: storedKeypair - }, function(err, generatedKeypair) { - if (err) { - callback(err); - return; - } + function initKeychain() { + // init user's local database + jsonDB.init(emailAddress); - if (generatedKeypair) { - // persist newly generated keypair - keychain.putUserKeyPair(generatedKeypair, callback); - } else { - callback(); - } - }); - } - }; + // call getUserKeyPair to read/sync keypair with devicestorage/cloud + self._keychain.getUserKeyPair(emailAddress, function(err, storedKeypair) { + if (err) { + callback(err); + return; + } + // init crypto + initCrypto(storedKeypair); + }); + } - /** - * Fetch an email with the following id - */ - self.getItem = function(folderName, itemId) { - var folder = self.account.get('folders').where({ - name: folderName - })[0]; - var mail = _.find(folder.get('items'), function(email) { - return email.id + '' === itemId + ''; - }); - return mail; - }; + function initCrypto(storedKeypair) { + crypto.init({ + emailAddress: emailAddress, + password: password, + keySize: account.get('symKeySize'), + rsaKeySize: account.get('asymKeySize'), + storedKeypair: storedKeypair + }, function(err, generatedKeypair) { + if (err) { + callback(err); + return; + } - /** - * Fetch a list of emails from the device's local storage - * @param offset [Number] The offset of items to fetch (0 is the last stored item) - * @param num [Number] The number of items to fetch (null means fetch all) - */ - self.listItems = function(folderName, offset, num, callback) { - var collection, folder; + if (generatedKeypair) { + // persist newly generated keypair + self._keychain.putUserKeyPair(generatedKeypair, callback); + } else { + callback(); + } + }); + } + }; - // check if items are in memory already (account.folders model) - folder = self.account.get('folders').where({ - name: folderName - })[0]; + /** + * Fetch an email with the following id + */ + EmailDAO.prototype.getItem = function(folderName, itemId) { + var self = this; - if (!folder) { - // get encrypted items from storage - devicestorage.listEncryptedItems('email_' + folderName, offset, num, function(err, encryptedList) { - if (err) { - callback(err); - return; - } - if (encryptedList.length === 0) { - callback(null, []); - return; - } + var folder = self.account.get('folders').where({ + name: folderName + })[0]; + var mail = _.find(folder.get('items'), function(email) { + return email.id + '' === itemId + ''; + }); + return mail; + }; - // decrypt list - crypto.decryptKeysAndList(encryptedList, function(err, decryptedList) { - if (err) { - callback(err); - return; - } + /** + * Fetch a list of emails from the device's local storage + * @param offset [Number] The offset of items to fetch (0 is the last stored item) + * @param num [Number] The number of items to fetch (null means fetch all) + */ + EmailDAO.prototype.listItems = function(folderName, offset, num, callback) { + var self = this, + collection, folder; - // cache collection in folder memory - if (decryptedList.length > 0) { - folder = new app.model.Folder({ - name: folderName - }); - folder.set('items', decryptedList); - self.account.get('folders').add(folder); - } + // check if items are in memory already (account.folders model) + folder = self.account.get('folders').where({ + name: folderName + })[0]; - callback(null, decryptedList); - }); - }); + if (!folder) { + // get encrypted items from storage + devicestorage.listEncryptedItems('email_' + folderName, offset, num, function(err, encryptedList) { + if (err) { + callback(err); + return; + } + if (encryptedList.length === 0) { + callback(null, []); + return; + } - } else { - // read items from memory - collection = folder.get('items'); - callback(null, collection); - } - }; + // decrypt list + crypto.decryptKeysAndList(encryptedList, function(err, decryptedList) { + if (err) { + callback(err); + return; + } - /** - * Synchronize a folder's items from the cloud to the device-storage - * @param folderName [String] The name of the folder e.g. 'inbox' - */ - self.syncFromCloud = function(folderName, callback) { - var folder, already, pubkeyIds = []; + // cache collection in folder memory + if (decryptedList.length > 0) { + folder = new app.model.Folder({ + name: folderName + }); + folder.set('items', decryptedList); + self.account.get('folders').add(folder); + } - // fetch most recent date - this.listItems(folderName, 0, 1, function(err, localItems) { - if (err) { - callback(err); // error - return; - } + callback(null, decryptedList); + }); + }); - var filter = ''; - if (localItems && localItems.length > 0) { - // get gmt date since that's what the storage service seems to use - var sentDate = localItems[localItems.length - 1].sentDate; - var date = util.parseDate(sentDate); - date.setHours(date.getHours() + (date.getTimezoneOffset() / 60)); - var gmtDate = util.formatDate(date); + } else { + // read items from memory + collection = folder.get('items'); + callback(null, collection); + } + }; - // sync delta of last item sent date - filter = '?date=' + gmtDate; - startSync(filter); + /** + * Synchronize a folder's items from the cloud to the device-storage + * @param folderName [String] The name of the folder e.g. 'inbox' + */ + EmailDAO.prototype.syncFromCloud = function(folderName, callback) { + var self = this, + folder, already, pubkeyIds = []; - } else { - // do a full sync of all items on the cloud - startSync(filter); - } - }); + // fetch most recent date + this.listItems(folderName, 0, 1, function(err, localItems) { + if (err) { + callback(err); // error + return; + } - function startSync(filter) { - // fetch items from the cloud - cloudstorage.listEncryptedItems('email', self.account.get('emailAddress'), folderName + filter, function(err, encryptedList) { - // return if an error occured - if (err) { - callback({ - errMsg: 'Syncing encrypted items from cloud failed!', - err: err - }); // error - return; - } - if (encryptedList.length === 0) { - callback(); - return; - } + var filter = ''; + if (localItems && localItems.length > 0) { + // get gmt date since that's what the storage service seems to use + var sentDate = localItems[localItems.length - 1].sentDate; + var date = util.parseDate(sentDate); + date.setHours(date.getHours() + (date.getTimezoneOffset() / 60)); + var gmtDate = util.formatDate(date); - // TODO: remove old folder items from devicestorage + // sync delta of last item sent date + filter = '?date=' + gmtDate; + startSync(filter); - reencryptItems(encryptedList); - }); - } + } else { + // do a full sync of all items on the cloud + startSync(filter); + } + }); - function reencryptItems(encryptedList) { - // gather public key ids required to verify signatures - encryptedList.forEach(function(i) { - already = null; - already = _.findWhere(pubkeyIds, { - _id: i.senderPk - }); - if (!already) { - pubkeyIds.push({ - _id: i.senderPk - }); - } - }); + function startSync(filter) { + // fetch items from the cloud + self._cloudstorage.listEncryptedItems('email', self.account.get('emailAddress'), folderName + filter, function(err, encryptedList) { + // return if an error occured + if (err) { + callback({ + errMsg: 'Syncing encrypted items from cloud failed!', + err: err + }); // error + return; + } + if (encryptedList.length === 0) { + callback(); + return; + } - // fetch public keys from keychain - keychain.getPublicKeys(pubkeyIds, function(err, senderPubkeys) { - if (err) { - callback(err); - return; - } + // TODO: remove old folder items from devicestorage - // verfiy signatures and re-encrypt item keys - crypto.reencryptListKeysForUser(encryptedList, senderPubkeys, function(err, encryptedKeyList) { - if (err) { - callback(err); - return; - } + reencryptItems(encryptedList); + }); + } - // persist encrypted list in device storage - devicestorage.storeEcryptedList(encryptedKeyList, 'email_' + folderName, function() { - // remove cached folder in account model - folder = self.account.get('folders').where({ - name: folderName - })[0]; - if (folder) { - self.account.get('folders').remove(folder); - } - callback(); - }); - }); - }); - } - }; + function reencryptItems(encryptedList) { + // gather public key ids required to verify signatures + encryptedList.forEach(function(i) { + already = null; + already = _.findWhere(pubkeyIds, { + _id: i.senderPk + }); + if (!already) { + pubkeyIds.push({ + _id: i.senderPk + }); + } + }); - /** - * Send a plaintext Email to the user's outbox in the cloud - */ - self.sendEmail = function(email, callback) { - var userId = self.account.get('emailAddress'); + // fetch public keys from keychain + self._keychain.getPublicKeys(pubkeyIds, function(err, senderPubkeys) { + if (err) { + callback(err); + return; + } - // validate email addresses - var invalidRecipient; - _.each(email.to, function(i) { - if (!validateEmail(i.address)) { - invalidRecipient = i.address; - } - }); - if (invalidRecipient) { - callback({ - errMsg: 'Invalid recipient: ' + invalidRecipient - }); - return; - } - if (!validateEmail(email.from[0].address)) { - callback({ - errMsg: 'Invalid sender: ' + email.from - }); - return; - } + // verfiy signatures and re-encrypt item keys + crypto.reencryptListKeysForUser(encryptedList, senderPubkeys, function(err, encryptedKeyList) { + if (err) { + callback(err); + return; + } - // generate a new UUID for the new email - email.id = util.UUID(); - // set sent date - email.sentDate = util.formatDate(new Date()); + // persist encrypted list in device storage + devicestorage.storeEcryptedList(encryptedKeyList, 'email_' + folderName, function() { + // remove cached folder in account model + folder = self.account.get('folders').where({ + name: folderName + })[0]; + if (folder) { + self.account.get('folders').remove(folder); + } + callback(); + }); + }); + }); + } + }; - // only support single recipient for e-2-e encryption - var recipient = email.to[0].address; + /** + * Send a plaintext Email to the user's outbox in the cloud + */ + EmailDAO.prototype.sendEmail = function(email, callback) { + var self = this, + userId = self.account.get('emailAddress'); - // check if receiver has a public key - keychain.getReveiverPublicKey(recipient, function(err, receiverPubkey) { - if (err) { - callback(err); - return; - } + // validate email addresses + var invalidRecipient; + _.each(email.to, function(i) { + if (!validateEmail(i.address)) { + invalidRecipient = i.address; + } + }); + if (invalidRecipient) { + callback({ + errMsg: 'Invalid recipient: ' + invalidRecipient + }); + return; + } + if (!validateEmail(email.from[0].address)) { + callback({ + errMsg: 'Invalid sender: ' + email.from + }); + return; + } - if (receiverPubkey) { - // public key found... encrypt and send - encrypt(email, receiverPubkey); - } else { - // no public key found... send plaintext mail via SMTP - send(email); - } - }); + // generate a new UUID for the new email + email.id = util.UUID(); + // set sent date + email.sentDate = util.formatDate(new Date()); - function encrypt(email, receiverPubkey) { - // encrypt the email - crypto.encryptListForUser([email], [receiverPubkey], function(err, encryptedList) { - if (err) { - callback(err); - return; - } + // only support single recipient for e-2-e encryption + var recipient = email.to[0].address; - var ct = encryptedList[0]; + // check if receiver has a public key + self._keychain.getReveiverPublicKey(recipient, function(err, receiverPubkey) { + if (err) { + callback(err); + return; + } - var envelope = { - id: email.id, - crypto: 'rsa-1024-sha-256-aes-128-cbc', - sentDate: email.sentDate, - ciphertext: ct.ciphertext, - encryptedKey: ct.encryptedKey, - iv: ct.iv, - signature: ct.signature, - senderPk: ct.senderPk - }; + if (receiverPubkey) { + // public key found... encrypt and send + encrypt(email, receiverPubkey); + } else { + // no public key found... send plaintext mail via SMTP + send(email); + } + }); - send(envelope); - }); - } + function encrypt(email, receiverPubkey) { + // encrypt the email + crypto.encryptListForUser([email], [receiverPubkey], function(err, encryptedList) { + if (err) { + callback(err); + return; + } - function send(email) { - if (smtpClient) { - // send email directly client side - smtpClient.send(email, callback); - } else { - // send email via cloud service - cloudstorage.deliverEmail(email, userId, recipient, function(err) { - callback(err); - }); - } - } - }; - }; + var ct = encryptedList[0]; - // - // helper functions - // + var envelope = { + id: email.id, + crypto: 'rsa-1024-sha-256-aes-128-cbc', + sentDate: email.sentDate, + ciphertext: ct.ciphertext, + encryptedKey: ct.encryptedKey, + iv: ct.iv, + signature: ct.signature, + senderPk: ct.senderPk + }; - function validateEmail(email) { - var re = /^(([^<>()[\]\\.,;:\s@\"]+(\.[^<>()[\]\\.,;:\s@\"]+)*)|(\".+\"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/; - return re.test(email); - } + send(envelope); + }); + } - return EmailDAO; + function send(email) { + if (self._smtpClient) { + // send email directly client side + self._smtpClient.send(email, callback); + } else { + // send email via cloud service + self._cloudstorage.deliverEmail(email, userId, recipient, function(err) { + callback(err); + }); + } + } + }; + + // + // helper functions + // + + function validateEmail(email) { + var re = /^(([^<>()[\]\\.,;:\s@\"]+(\.[^<>()[\]\\.,;:\s@\"]+)*)|(\".+\"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/; + return re.test(email); + } + + return EmailDAO; }); \ No newline at end of file