diff --git a/src/js/app-config.js b/src/js/app-config.js index 392639b..64ec102 100644 --- a/src/js/app-config.js +++ b/src/js/app-config.js @@ -12,6 +12,7 @@ module.exports = appCfg; * Global app configurations */ appCfg.config = { + pgpComment: 'Whiteout Mail - https://whiteout.io', keyServerUrl: 'https://keys.whiteout.io', hkpUrl: 'http://keyserver.ubuntu.com', privkeyServerUrl: 'https://keychain.whiteout.io', diff --git a/src/js/controller/app/read.js b/src/js/controller/app/read.js index bae4f48..2f5ecad 100644 --- a/src/js/controller/app/read.js +++ b/src/js/controller/app/read.js @@ -6,8 +6,6 @@ var ReadCtrl = function($scope, $location, $q, email, invitation, outbox, pgp, keychain, appConfig, download, auth, dialog, status) { - var str = appConfig.string; - // // scope state // @@ -158,18 +156,10 @@ var ReadCtrl = function($scope, $location, $q, email, invitation, outbox, pgp, k }); }).then(function() { - var invitationMail = { - from: [{ - address: sender - }], - to: [{ - address: recipient - }], - cc: [], - bcc: [], - subject: str.invitationSubject, - body: str.invitationMessage - }; + var invitationMail = invitation.createMail({ + sender: sender, + recipient: recipient + }); // send invitation mail return outbox.put(invitationMail); diff --git a/src/js/controller/app/write.js b/src/js/controller/app/write.js index 6ab5b86..caa4835 100644 --- a/src/js/controller/app/write.js +++ b/src/js/controller/app/write.js @@ -6,7 +6,7 @@ var util = require('crypto-lib').util; // Controller // -var WriteCtrl = function($scope, $window, $filter, $q, appConfig, auth, keychain, pgp, email, outbox, dialog, axe, status) { +var WriteCtrl = function($scope, $window, $filter, $q, appConfig, auth, keychain, pgp, email, outbox, dialog, axe, status, invitation) { var str = appConfig.string; var cfg = appConfig.config; @@ -52,6 +52,8 @@ var WriteCtrl = function($scope, $window, $filter, $q, appConfig, auth, keychain $scope.body = ''; $scope.attachments = []; $scope.addressBookCache = undefined; + $scope.showInvite = undefined; + $scope.invited = []; } function reportBug() { @@ -248,6 +250,9 @@ var WriteCtrl = function($scope, $window, $filter, $q, appConfig, auth, keychain recipient.key = key; recipient.secure = true; } + } else { + // show invite dialog if no key found + $scope.showInvite = true; } $scope.checkSendStatus(); @@ -286,6 +291,7 @@ var WriteCtrl = function($scope, $window, $filter, $q, appConfig, auth, keychain // only allow sending if receviers exist if (numReceivers < 1) { + $scope.showInvite = false; return; } @@ -299,6 +305,7 @@ var WriteCtrl = function($scope, $window, $filter, $q, appConfig, auth, keychain $scope.okToSend = true; $scope.sendBtnText = str.sendBtnSecure; $scope.sendBtnSecure = true; + $scope.showInvite = false; } else { // send plaintext $scope.okToSend = true; @@ -315,6 +322,56 @@ var WriteCtrl = function($scope, $window, $filter, $q, appConfig, auth, keychain $scope.attachments.splice($scope.attachments.indexOf(attachment), 1); }; + /** + * Invite all users without a public key + */ + $scope.invite = function() { + var sender = auth.emailAddress, + sendJobs = [], + invitees = []; + + $scope.showInvite = false; + + // get recipients with no keys + $scope.to.forEach(check); + $scope.cc.forEach(check); + $scope.bcc.forEach(check); + + function check(recipient) { + if (util.validateEmailAddress(recipient.address) && !recipient.secure && $scope.invited.indexOf(recipient.address) === -1) { + invitees.push(recipient.address); + } + } + + return $q(function(resolve) { + resolve(); + + }).then(function() { + invitees.forEach(function(recipientAddress) { + var invitationMail = invitation.createMail({ + sender: sender, + recipient: recipientAddress + }); + // send invitation mail + var promise = outbox.put(invitationMail).then(function() { + return invitation.invite({ + recipient: recipientAddress, + sender: sender + }); + }); + sendJobs.push(promise); + // remember already invited users to prevent spamming + $scope.invited.push(recipientAddress); + }); + + return Promise.all(sendJobs); + + }).catch(function(err) { + $scope.showInvite = true; + return dialog.error(err); + }); + }; + // // Editing email body // diff --git a/src/js/crypto/pgp.js b/src/js/crypto/pgp.js index a048fd6..807eb65 100644 --- a/src/js/crypto/pgp.js +++ b/src/js/crypto/pgp.js @@ -11,7 +11,7 @@ var util = openpgp.util, * High level crypto api that handles all calls to OpenPGP.js */ function PGP() { - openpgp.config.commentstring = 'Whiteout Mail - https://whiteout.io'; + openpgp.config.commentstring = config.pgpComment; openpgp.config.prefer_hash_algorithm = openpgp.enums.hash.sha256; openpgp.initWorker(config.workerPath + '/openpgp.worker.min.js'); } diff --git a/src/js/email/outbox.js b/src/js/email/outbox.js index e0479c8..f84d542 100644 --- a/src/js/email/outbox.js +++ b/src/js/email/outbox.js @@ -62,6 +62,12 @@ Outbox.prototype.put = function(mail) { var self = this, allReaders = mail.from.concat(mail.to.concat(mail.cc.concat(mail.bcc))); // all the users that should be able to read the mail + if (mail.to.concat(mail.cc.concat(mail.bcc)).length === 0) { + return new Promise(function() { + throw new Error('Message has no recipients!'); + }); + } + mail.publicKeysArmored = []; // gather the public keys mail.uid = mail.id = util.UUID(); // the mail needs a random id & uid for storage in the database diff --git a/src/js/service/invitation.js b/src/js/service/invitation.js index 377378a..317a768 100644 --- a/src/js/service/invitation.js +++ b/src/js/service/invitation.js @@ -8,13 +8,33 @@ module.exports = Invitation; * The Invitation is a high level Data Access Object that access the invitation service REST endpoint. * @param {Object} restDao The REST Data Access Object abstraction */ -function Invitation(invitationRestDao) { +function Invitation(invitationRestDao, appConfig) { this._restDao = invitationRestDao; + this._appConfig = appConfig; } -// -// API -// +/** + * Create the invitation mail object + * @param {String} options.sender The sender's email address + * @param {String} options.recipient The recipient's email address + * @return {Object} The mail object + */ +Invitation.prototype.createMail = function(options) { + var str = this._appConfig.string; + + return { + from: [{ + address: options.sender + }], + to: [{ + address: options.recipient + }], + cc: [], + bcc: [], + subject: str.invitationSubject, + body: str.invitationMessage + }; +}; /** * Notes an invite for the recipient by the sender in the invitation web service diff --git a/src/sass/blocks/views/_write.scss b/src/sass/blocks/views/_write.scss index d7e2257..0748436 100644 --- a/src/sass/blocks/views/_write.scss +++ b/src/sass/blocks/views/_write.scss @@ -21,6 +21,33 @@ margin-top: 0.5em; } } + &__invite { + position: relative; + margin-top: 1.3em; + border: 1px solid $color-red-light; + + p { + color: $color-red-light; + margin: 0.7em 1em; + + svg { + width: 1em; + height: 1em; + fill: $color-red-light; + + // for better valignment + position: relative; + top: 0.15em; + margin-right: 0.3em; + } + } + + .btn { + position: absolute; + top: 5px; + right: 5px; + } + } &__subject { position: relative; margin-top: 1.3em; diff --git a/src/tpl/write.html b/src/tpl/write.html index 1ec6bc8..c1768d0 100644 --- a/src/tpl/write.html +++ b/src/tpl/write.html @@ -41,6 +41,18 @@ +
+ + Key not found! + Invite user to encrypt. +
+ +