mirror of
https://github.com/moparisthebest/mail
synced 2025-02-07 02:20:14 -05:00
refactor email dao for prototype style and also use spaces instead of tabs
This commit is contained in:
parent
2186d20a7c
commit
553320adc8
@ -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;
|
||||||
});
|
});
|
Loading…
Reference in New Issue
Block a user