1
0
mirror of https://github.com/moparisthebest/mail synced 2024-11-23 01:12:19 -05:00

allow plaintext sending and remove whiteout tag from subject

This commit is contained in:
Tankred Hase 2014-02-27 15:23:33 +01:00
parent 69a222e46a
commit badea7ab8a
8 changed files with 92 additions and 205 deletions

View File

@ -53,15 +53,15 @@ define(function(require) {
subjectPrefix: '[whiteout] ', subjectPrefix: '[whiteout] ',
fallbackSubject: '(no subject)', fallbackSubject: '(no subject)',
invitationSubject: 'Invitation to a private conversation', invitationSubject: 'Invitation to a private conversation',
invitationMessage: 'I would like to invite you to a private conversation!\n\nIn order to read my encrypted message please install the Whiteout Mail application. This application is used to read and write messages securely with strong encryption applied.\n\nGo to the Whiteout Networks homepage to learn more and to download the application: https://whiteout.io', invitationMessage: 'Hi,\n\nI would like to invite you to a private conversation!\n\nPlease install the Whiteout Mail application. This application is used to read and write messages securely with strong 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.\n\nOpen Whiteout Mail: https://chrome.google.com/webstore/detail/jjgghafhamholjigjoghcfcekhkonijg\n\n', message: 'Hi,\n\nthis is a private conversation. To read my encrypted message below, simply open it in Whiteout Mail.\n\nOpen Whiteout Mail: https://chrome.google.com/webstore/detail/jjgghafhamholjigjoghcfcekhkonijg\n\n',
cryptPrefix: '-----BEGIN PGP MESSAGE-----', cryptPrefix: '-----BEGIN PGP MESSAGE-----',
cryptSuffix: '-----END PGP MESSAGE-----', cryptSuffix: '-----END PGP MESSAGE-----',
signature: 'Sent securely from Whiteout Mail', signature: '\n\n\n--\nSent from Whiteout Mail - get the app for easy end-to-end encryption\nhttps://whiteout.io\n\n',
webSite: 'http://whiteout.io', webSite: 'http://whiteout.io',
verificationSubject: 'New public key uploaded', verificationSubject: 'New public key uploaded',
sendBtnInvite: 'Invite & send securely', sendBtnClear: 'Send',
sendBtnSecure: 'Send securely' sendBtnSecure: 'Secure send'
}; };
return app; return app;

View File

@ -348,17 +348,15 @@ define(function(require) {
// init objects and inject dependencies // init objects and inject dependencies
restDao = new RestDAO(); restDao = new RestDAO();
pubkeyDao = new PublicKeyDAO(restDao); pubkeyDao = new PublicKeyDAO(restDao);
invitationDao = new InvitationDAO(restDao);
lawnchairDao = new LawnchairDAO(); lawnchairDao = new LawnchairDAO();
userStorage = new DeviceStorageDAO(lawnchairDao); userStorage = new DeviceStorageDAO(lawnchairDao);
keychain = new KeychainDAO(lawnchairDao, pubkeyDao); self._invitationDao = invitationDao = new InvitationDAO(restDao);
self._keychain = keychain; self._keychain = keychain = new KeychainDAO(lawnchairDao, pubkeyDao);
pgp = new PGP(); self._crypto = pgp = new PGP();
self._crypto = pgp;
self._pgpbuilder = pgpbuilder = new PgpBuilder(); self._pgpbuilder = pgpbuilder = new PgpBuilder();
self._emailDao = emailDao = new EmailDAO(keychain, pgp, userStorage, pgpbuilder, mailreader); self._emailDao = emailDao = new EmailDAO(keychain, pgp, userStorage, pgpbuilder, mailreader);
self._outboxBo = new OutboxBO(emailDao, keychain, userStorage, invitationDao); self._outboxBo = new OutboxBO(emailDao, keychain, userStorage);
}; };
/** /**

View File

@ -3,9 +3,7 @@ define(function(require) {
var _ = require('underscore'), var _ = require('underscore'),
util = require('cryptoLib/util'), util = require('cryptoLib/util'),
str = require('js/app-config').string,
config = require('js/app-config').config, config = require('js/app-config').config,
InvitationDAO = require('js/dao/invitation-dao'),
outboxDb = 'email_OUTBOX'; outboxDb = 'email_OUTBOX';
/** /**
@ -13,7 +11,7 @@ define(function(require) {
* The local outbox takes care of the emails before they are being sent. * The local outbox takes care of the emails before they are being sent.
* It also checks periodically if there are any mails in the local device storage to be sent. * It also checks periodically if there are any mails in the local device storage to be sent.
*/ */
var OutboxBO = function(emailDao, keychain, devicestorage, invitationDao) { var OutboxBO = function(emailDao, keychain, devicestorage) {
/** @private */ /** @private */
this._emailDao = emailDao; this._emailDao = emailDao;
@ -23,10 +21,6 @@ define(function(require) {
/** @private */ /** @private */
this._devicestorage = devicestorage; this._devicestorage = devicestorage;
/** @private */
this._invitationDao = invitationDao;
/** /**
* Semaphore-esque flag to avoid 'concurrent' calls to _processOutbox when the timeout fires, but a call is still in process. * Semaphore-esque flag to avoid 'concurrent' calls to _processOutbox when the timeout fires, but a call is still in process.
* @private */ * @private */
@ -64,7 +58,6 @@ define(function(require) {
allReaders = mail.from.concat(mail.to.concat(mail.cc.concat(mail.bcc))); // all the users that should be able to read the mail allReaders = mail.from.concat(mail.to.concat(mail.cc.concat(mail.bcc))); // all the users that should be able to read the mail
mail.publicKeysArmored = []; // gather the public keys mail.publicKeysArmored = []; // gather the public keys
mail.unregisteredUsers = []; // gather the recipients for which no public key is available
mail.id = util.UUID(); // the mail needs a random uuid for storage in the database mail.id = util.UUID(); // the mail needs a random uuid for storage in the database
checkRecipients(allReaders); checkRecipients(allReaders);
@ -87,8 +80,6 @@ define(function(require) {
// otherwise remember the recipient as unregistered for later sending // otherwise remember the recipient as unregistered for later sending
if (key) { if (key) {
mail.publicKeysArmored.push(key.publicKey); mail.publicKeysArmored.push(key.publicKey);
} else {
mail.unregisteredUsers.push(recipient);
} }
after(); after();
@ -96,8 +87,15 @@ define(function(require) {
}); });
} }
// encrypts the body and attachments and persists the mail object
function encryptAndPersist() { function encryptAndPersist() {
// only encrypt if all recipients have public keys
if (mail.publicKeysArmored.length < allReaders.length) {
self._devicestorage.storeList([mail], outboxDb, callback);
return;
}
// encrypts the body and attachments and persists the mail object
self._emailDao.encrypt({ self._emailDao.encrypt({
mail: mail, mail: mail,
publicKeysArmored: mail.publicKeysArmored publicKeysArmored: mail.publicKeysArmored
@ -152,90 +150,27 @@ define(function(require) {
// send pending mails if possible // send pending mails if possible
pendingMails.forEach(function(mail) { pendingMails.forEach(function(mail) {
handleMail(mail, after); send(mail, after);
}); });
}); });
// if we can send the mail, do that. otherwise check if there are users that need to be invited // send the message
function handleMail(mail, done) { function send(mail, done) {
// no unregistered users, go straight to send
if (mail.unregisteredUsers.length === 0) { // check is email is to be sent encrypted or as plaintex
send(mail, done); if (mail.encrypted === true) {
return; // email was already encrypted before persisting in outbox, tell pgpmailer to send encrypted and not encrypt again
self._emailDao.sendEncrypted({
email: mail
}, onSend);
} else {
// send email as plaintext
self._emailDao.sendPlaintext({
email: mail
}, onSend);
} }
var after = _.after(mail.unregisteredUsers.length, function() { function onSend(err) {
// invite unregistered users if necessary
if (mail.unregisteredUsers.length > 0) {
unsentMails++;
self._invite({
sender: mail.from[0],
recipients: mail.unregisteredUsers
}, done);
return;
}
// there are public keys available for the missing users,
// so let's re-encrypt the mail for them and send it
reencryptAndSend(mail, done);
});
// find out if the unregistered users have registered in the meantime
mail.unregisteredUsers.forEach(function(recipient) {
self._keychain.getReceiverPublicKey(recipient.address, function(err, key) {
var index;
if (err) {
self._outboxBusy = false;
callback(err);
return;
}
if (key) {
// remove the newly joined users from the unregistered users
index = mail.unregisteredUsers.indexOf(recipient);
mail.unregisteredUsers.splice(index, 1);
mail.publicKeysArmored.push(key.publicKey);
}
after();
});
});
}
// all the recipients have public keys available, so let's re-encrypt the mail
// to make it available for them, too
function reencryptAndSend(mail, done) {
self._emailDao.reEncrypt({
mail: mail,
publicKeysArmored: mail.publicKeysArmored
}, function(err) {
if (err) {
self._outboxBusy = false;
callback(err);
return;
}
// stores the newly encrypted mail object to disk in case something funky
// happens during sending and we need do re-send the mail later.
// avoids doing the encryption twice...
self._devicestorage.storeList([mail], outboxDb, function(err) {
if (err) {
self._outboxBusy = false;
callback(err);
return;
}
send(mail, done);
});
});
}
// send the encrypted message
function send(mail, done) {
self._emailDao.sendEncrypted({
email: mail
}, function(err) {
if (err) { if (err) {
self._outboxBusy = false; self._outboxBusy = false;
if (err.code === 42) { if (err.code === 42) {
@ -255,7 +190,7 @@ define(function(require) {
if (typeof self.onSent === 'function') { if (typeof self.onSent === 'function') {
self.onSent(mail); self.onSent(mail);
} }
}); }
} }
// removes the mail object from disk after successfully sending it // removes the mail object from disk after successfully sending it
@ -272,90 +207,5 @@ define(function(require) {
} }
}; };
/**
* Sends an invitation mail to an array of users that have no public key available yet
* @param {Array} recipients Array of objects with information on the sender (name, address)
* @param {Function} callback Invoked when the mail was sent
*/
OutboxBO.prototype._invite = function(options, callback) {
var self = this,
sender = options.sender;
var after = _.after(options.recipients.length, callback);
options.recipients.forEach(function(recipient) {
checkInvitationStatus(recipient, after);
});
// checks the invitation status. if an invitation is pending, we do not need to resend the invitation mail
function checkInvitationStatus(recipient, done) {
self._invitationDao.check({
recipient: recipient.address,
sender: sender.address
}, function(err, status) {
if (err) {
callback(err);
return;
}
if (status === InvitationDAO.INVITE_PENDING) {
// the recipient is already invited, we're done here.
done();
return;
}
invite(recipient, done);
});
}
// let's invite the recipient and send him a mail to inform him to join whiteout
function invite(recipient, done) {
self._invitationDao.invite({
recipient: recipient.address,
sender: sender.address
}, function(err, status) {
if (err) {
callback(err);
return;
}
if (status !== InvitationDAO.INVITE_SUCCESS) {
callback({
errMsg: 'Could not successfully invite ' + recipient
});
return;
}
var invitationMail = {
from: [sender],
to: [recipient],
subject: str.invitationSubject,
body: 'Hi,\n\n' + str.invitationMessage + '\n\n'
};
// send invitation mail
self._emailDao.sendPlaintext({
email: invitationMail
}, function(err) {
if (err) {
if (err.code === 42) {
// offline try again later
done();
} else {
callback(err);
}
return;
}
// fire sent notification
if (typeof self.onSent === 'function') {
self.onSent(invitationMail);
}
done();
});
});
}
};
return OutboxBO; return OutboxBO;
}); });

View File

@ -4,7 +4,8 @@ define(function(require) {
var appController = require('js/app-controller'), var appController = require('js/app-controller'),
download = require('js/util/download'), download = require('js/util/download'),
angular = require('angular'), angular = require('angular'),
emailDao, crypto, keychain; str = require('js/app-config').string,
emailDao, invitationDao, outbox, crypto, keychain;
// //
// Controller // Controller
@ -13,6 +14,8 @@ define(function(require) {
var ReadCtrl = function($scope) { var ReadCtrl = function($scope) {
emailDao = appController._emailDao; emailDao = appController._emailDao;
invitationDao = appController._invitationDao;
outbox = appController._outboxBo;
crypto = appController._crypto; crypto = appController._crypto;
keychain = appController._keychain; keychain = appController._keychain;
@ -112,6 +115,29 @@ define(function(require) {
}, $scope.onError); }, $scope.onError);
} }
}; };
$scope.inviteUser = function(address) {
invitationDao.invite({
recipient: address,
sender: emailDao._account.emailAddress
}, function(err) {
if (err) {
$scope.onError(err);
return;
}
var invitationMail = {
from: [emailDao._account.emailAddress],
to: [address],
subject: str.invitationSubject,
body: str.invitationMessage
};
// send invitation mail
outbox.put(invitationMail, $scope.onError);
});
};
}; };
// //

View File

@ -171,16 +171,21 @@ define(function(require) {
} }
} }
// sender can invite only one use at a time // only allow sending if receviers exist
if (!allSecure && numReceivers === 1) { if (numReceivers < 1) {
$scope.sendBtnText = str.sendBtnInvite; return;
}
if (allSecure) {
// send encrypted if all secure
$scope.okToSend = true; $scope.okToSend = true;
$scope.sendBtnSecure = false;
} else if (allSecure && numReceivers > 0) {
// all recipients are secure
$scope.sendBtnText = str.sendBtnSecure; $scope.sendBtnText = str.sendBtnSecure;
$scope.okToSend = true;
$scope.sendBtnSecure = true; $scope.sendBtnSecure = true;
} else {
// send plaintext
$scope.okToSend = true;
$scope.sendBtnText = str.sendBtnClear;
$scope.sendBtnSecure = false;
} }
}; };

View File

@ -1034,9 +1034,6 @@ define(function(require) {
return; return;
} }
// add whiteout tag to subject
options.email.subject = str.subjectPrefix + options.email.subject;
// mime encode, sign, encrypt and send email via smtp // mime encode, sign, encrypt and send email via smtp
self._pgpMailer.send({ self._pgpMailer.send({
encrypt: true, encrypt: true,
@ -1055,8 +1052,8 @@ define(function(require) {
return; return;
} }
// add whiteout tag to subject // append the signature to plaintext mails
options.email.subject = str.subjectPrefix + options.email.subject; options.email.body += str.signature;
// mime encode, sign and send email via smtp // mime encode, sign and send email via smtp
this._pgpMailer.send({ this._pgpMailer.send({
@ -1068,10 +1065,6 @@ define(function(require) {
this._pgpbuilder.encrypt(options, callback); this._pgpbuilder.encrypt(options, callback);
}; };
EmailDAO.prototype.reEncrypt = function(options, callback) {
this._pgpbuilder.reEncrypt(options, callback);
};
// //
// Internal API // Internal API
// //

View File

@ -57,6 +57,21 @@
} }
} }
.btn-primary {
background-color: $label-primary-back-color;
color: $label-primary-color;
background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAYAAAAGCAYAAADgzO9IAAAAJUlEQVQIW2NkQABJIPM5lCvJCGMgC4LYIAkUlTAFMB0gjRQaBQCw8go5lVnl5wAAAABJRU5ErkJggg==);
@include respond-to(retina) {
background-size: 3px 3px;
}
&:hover,
&:focus {
background-color: lighten($label-primary-back-color, 5%);
}
}
.btn-alt { .btn-alt {
background-color: $color-grey-input; background-color: $color-grey-input;

View File

@ -46,13 +46,13 @@
<div class="body" focus-child> <div class="body" focus-child>
<p ng-model="body" contentEditable="true" spellcheck="false" ng-change="updatePreview()" tabindex="3" focus-me="state.writer.open && writerTitle === 'Reply'"></p> <p ng-model="body" contentEditable="true" spellcheck="false" ng-change="updatePreview()" tabindex="3" focus-me="state.writer.open && writerTitle === 'Reply'"></p>
<div class="encrypt-preview" ng-class="{'invisible': !ciphertextPreview}"> <div class="encrypt-preview" ng-class="{'invisible': !ciphertextPreview || !sendBtnSecure}">
<p>-----BEGIN ENCRYPTED PREVIEW-----<br>{{ciphertextPreview}}<br>-----END ENCRYPTED PREVIEW-----</p> <p>-----BEGIN ENCRYPTED PREVIEW-----<br>{{ciphertextPreview}}<br>-----END ENCRYPTED PREVIEW-----</p>
</div><!--/.encrypt-preview--> </div><!--/.encrypt-preview-->
</div><!--/.body--> </div><!--/.body-->
<div class="send-control"> <div class="send-control">
<button ng-click="sendToOutbox()" class="btn" data-icon="{{(sendBtnSecure === false) ? '&#xe001;' : (sendBtnSecure === true) ? '&#xe009;' : ''}}" ng-disabled="!okToSend" tabindex="4">{{sendBtnText || 'Send'}}</button> <button ng-click="sendToOutbox()" class="btn" ng-class="{'btn-primary': sendBtnSecure === false}" ng-disabled="!okToSend" tabindex="4">{{sendBtnText || 'Send'}}</button>
</div> </div>
</div><!--/.write-view--> </div><!--/.write-view-->