1
0
mirror of https://github.com/moparisthebest/mail synced 2024-08-13 16:43:47 -04:00

refactor email dao for prototype style and also use spaces instead of tabs

This commit is contained in:
Tankred Hase 2013-08-16 20:50:47 +02:00
parent 2186d20a7c
commit 553320adc8

View File

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