mirror of
https://github.com/moparisthebest/mail
synced 2024-08-13 16:43:47 -04:00
Compare commits
43 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
43729833a5 | ||
|
b05aeea342 | ||
|
137c8c7c24 | ||
|
853be194d9 | ||
|
8b36a719c3 | ||
|
8165416c5d | ||
|
b375d81635 | ||
|
e56f8c2c28 | ||
|
ad3691fae9 | ||
|
e0663ab8d8 | ||
|
263b5c13b0 | ||
|
8636ba201b | ||
|
aa24881efc | ||
|
b7b5c1bdf5 | ||
|
59d38f9b14 | ||
|
c44984b2f3 | ||
|
c31c320e83 | ||
|
7f49e691db | ||
|
a346f1612e | ||
|
6b0b71d4ff | ||
|
4683583a0a | ||
|
b038ac2c16 | ||
|
f32863dc54 | ||
|
39d19df187 | ||
|
9bf8c758ec | ||
|
76f770a12b | ||
|
3af376b419 | ||
|
e9a8702b39 | ||
|
25b9141a5f | ||
|
1d0efc02a2 | ||
|
c3362c193d | ||
|
7f0235c9b2 | ||
|
8e0dfacd51 | ||
|
467d001483 | ||
|
e7fb3bcf6d | ||
|
ce740b2109 | ||
|
0bfaba3bd9 | ||
|
e7cbf9ed86 | ||
|
c76a392abf | ||
|
ca8c2d9a4f | ||
|
f287c4cddf | ||
|
59006a98d7 | ||
|
73fcfba2a9 |
@ -1,10 +1,10 @@
|
|||||||
branch-defaults:
|
branch-defaults:
|
||||||
release/prod:
|
release/prod:
|
||||||
environment: mail-html5-prod
|
environment: mail-prod
|
||||||
release/test:
|
release/test:
|
||||||
environment: mail-html5-test
|
environment: mail-test
|
||||||
global:
|
global:
|
||||||
application_name: mail-html5
|
application_name: mail
|
||||||
default_ec2_keyname: null
|
default_ec2_keyname: null
|
||||||
default_platform: Node.js
|
default_platform: Node.js
|
||||||
default_region: eu-central-1
|
default_region: eu-central-1
|
||||||
|
@ -1,10 +1,7 @@
|
|||||||
|
sudo: false
|
||||||
language: node_js
|
language: node_js
|
||||||
node_js:
|
node_js:
|
||||||
- "0.10"
|
|
||||||
- "0.12"
|
- "0.12"
|
||||||
before_install:
|
before_install:
|
||||||
- gem install sass
|
- gem install sass
|
||||||
- npm install -g grunt-cli
|
- npm install -g grunt-cli
|
||||||
notifications:
|
|
||||||
email:
|
|
||||||
- build@whiteout.io
|
|
||||||
|
@ -282,7 +282,6 @@ module.exports = function(grunt) {
|
|||||||
},
|
},
|
||||||
app: {
|
app: {
|
||||||
src: [
|
src: [
|
||||||
'src/lib/winstore-jscompat.js',
|
|
||||||
'src/lib/underscore/underscore.js',
|
'src/lib/underscore/underscore.js',
|
||||||
'node_modules/jquery/dist/jquery.min.js',
|
'node_modules/jquery/dist/jquery.min.js',
|
||||||
'src/lib/angular/angular.js',
|
'src/lib/angular/angular.js',
|
||||||
@ -308,7 +307,7 @@ module.exports = function(grunt) {
|
|||||||
},
|
},
|
||||||
readSandbox: {
|
readSandbox: {
|
||||||
src: [
|
src: [
|
||||||
'node_modules/dompurify/purify.js',
|
'node_modules/dompurify/src/purify.js',
|
||||||
'node_modules/iframe-resizer/js/iframeResizer.contentWindow.min.js',
|
'node_modules/iframe-resizer/js/iframeResizer.contentWindow.min.js',
|
||||||
'src/js/controller/app/read-sandbox.js'
|
'src/js/controller/app/read-sandbox.js'
|
||||||
],
|
],
|
||||||
|
16
README.md
16
README.md
@ -1,4 +1,4 @@
|
|||||||
Whiteout Mail [![Build Status](https://travis-ci.org/whiteout-io/mail-html5.svg?branch=master)](https://travis-ci.org/whiteout-io/mail-html5) [![Gitter](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/whiteout-io/mail-html5?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
|
Whiteout Mail [![Build Status](https://travis-ci.org/whiteout-io/mail.svg?branch=master)](https://travis-ci.org/whiteout-io/mail)
|
||||||
==========
|
==========
|
||||||
|
|
||||||
Whiteout Mail is an easy to use email client with integrated OpenPGP encryption written in pure JavaScript. Download the official version under [whiteout.io](https://whiteout.io/#product).
|
Whiteout Mail is an easy to use email client with integrated OpenPGP encryption written in pure JavaScript. Download the official version under [whiteout.io](https://whiteout.io/#product).
|
||||||
@ -7,15 +7,17 @@ Whiteout Mail is an easy to use email client with integrated OpenPGP encryption
|
|||||||
|
|
||||||
### Features
|
### Features
|
||||||
|
|
||||||
You can read about product features and our future roadmap in our [FAQ](https://github.com/whiteout-io/mail-html5/wiki/FAQ).
|
You can read about product features and our future roadmap in our [FAQ](https://github.com/whiteout-io/mail/wiki/FAQ).
|
||||||
|
|
||||||
### Privacy and Security
|
### Privacy and Security
|
||||||
|
|
||||||
We take the privacy of your data very seriously. Here are some of the technical details:
|
We take the privacy of your data very seriously. Here are some of the technical details:
|
||||||
|
|
||||||
|
* The code has undergone a [full security audit](https://blog.whiteout.io/2015/06/11/whiteout-mail-1-0-and-security-audit-by-cure53/) by [Cure53](https://cure53.de).
|
||||||
|
|
||||||
* Messages are [encrypted end-to-end ](http://en.wikipedia.org/wiki/End-to-end_encryption) using the [OpenPGP](http://en.wikipedia.org/wiki/Pretty_Good_Privacy) standard. This means that only you and the recipient can read your mail. Your messages and private PGP key are stored only on your computer (in IndexedDB).
|
* Messages are [encrypted end-to-end ](http://en.wikipedia.org/wiki/End-to-end_encryption) using the [OpenPGP](http://en.wikipedia.org/wiki/Pretty_Good_Privacy) standard. This means that only you and the recipient can read your mail. Your messages and private PGP key are stored only on your computer (in IndexedDB).
|
||||||
|
|
||||||
* Users have the option to use [encrypted private key sync](https://github.com/whiteout-io/mail-html5/wiki/Secure-OpenPGP-Key-Pair-Synchronization-via-IMAP) if they want to use Whiteout on multiple devices.
|
* Users have the option to use [encrypted private key sync](https://github.com/whiteout-io/mail/wiki/Secure-OpenPGP-Key-Pair-Synchronization-via-IMAP) if they want to use Whiteout on multiple devices.
|
||||||
|
|
||||||
* [Content Security Policy (CSP)](http://www.html5rocks.com/en/tutorials/security/content-security-policy/) is enforced to prevent injection attacks.
|
* [Content Security Policy (CSP)](http://www.html5rocks.com/en/tutorials/security/content-security-policy/) is enforced to prevent injection attacks.
|
||||||
|
|
||||||
@ -25,7 +27,7 @@ We take the privacy of your data very seriously. Here are some of the technical
|
|||||||
|
|
||||||
* Like most native email clients, whiteout mail uses raw [TCP sockets](http://developer.chrome.com/apps/socket.html) to communicate directly with your mail server via IMAP/SMTP. TLS is used to protect your password and message data in transit.
|
* Like most native email clients, whiteout mail uses raw [TCP sockets](http://developer.chrome.com/apps/socket.html) to communicate directly with your mail server via IMAP/SMTP. TLS is used to protect your password and message data in transit.
|
||||||
|
|
||||||
* The app is deployed as a signed [Chrome Packaged App](https://developer.chrome.com/apps/about_apps.html) with [auditable static versions](https://github.com/whiteout-io/mail-html5/releases) in order to prevent [problems with host-based security](https://blog.whiteout.io/2014/04/13/heartbleed-and-javascript-crypto/).
|
* The app is deployed as a signed [Chrome Packaged App](https://developer.chrome.com/apps/about_apps.html) with [auditable static versions](https://github.com/whiteout-io/mail/releases) in order to prevent [problems with host-based security](https://blog.whiteout.io/2014/04/13/heartbleed-and-javascript-crypto/).
|
||||||
|
|
||||||
* The app can also be used from any modern web browser in environments where installing an app is not possible (e.g. a locked down corporate desktop). The IMAP/SMTP TLS sessions are still terminated in the user's browser using JS crypto ([Forge](https://github.com/digitalbazaar/forge)), but the encrypted TLS payload is proxied via [socket.io](http://socket.io/), due to the lack of raw sockets in the browser. **Please keep in mind that this mode of operation is not as secure as using the signed packaged app, since users must trust the webserver to deliver the correct code. This mode will still protect user against passive attacks like wiretapping (since PGP and TLS are still applied in the user's browser), but not against active attacks from the webserver. So it's best to decide which threat model applies to you.**
|
* The app can also be used from any modern web browser in environments where installing an app is not possible (e.g. a locked down corporate desktop). The IMAP/SMTP TLS sessions are still terminated in the user's browser using JS crypto ([Forge](https://github.com/digitalbazaar/forge)), but the encrypted TLS payload is proxied via [socket.io](http://socket.io/), due to the lack of raw sockets in the browser. **Please keep in mind that this mode of operation is not as secure as using the signed packaged app, since users must trust the webserver to deliver the correct code. This mode will still protect user against passive attacks like wiretapping (since PGP and TLS are still applied in the user's browser), but not against active attacks from the webserver. So it's best to decide which threat model applies to you.**
|
||||||
|
|
||||||
@ -37,11 +39,11 @@ We take the privacy of your data very seriously. Here are some of the technical
|
|||||||
|
|
||||||
* We will launch a bug bounty program later on for independent security researchers. If you find any security vulnerabilities, don't hesitate to contact us [security@whiteout.io](mailto:security@whiteout.io).
|
* We will launch a bug bounty program later on for independent security researchers. If you find any security vulnerabilities, don't hesitate to contact us [security@whiteout.io](mailto:security@whiteout.io).
|
||||||
|
|
||||||
* You can also just create an [issue](https://github.com/whiteout-io/mail-html5/issues) on GitHub if you're missing a feature or just want to give us feedback. It would be much appreciated!
|
* You can also just create an [issue](https://github.com/whiteout-io/mail/issues) on GitHub if you're missing a feature or just want to give us feedback. It would be much appreciated!
|
||||||
|
|
||||||
### Testing
|
### Testing
|
||||||
|
|
||||||
You can download a prebuilt bundle under [releases](https://github.com/whiteout-io/mail-html5/releases) or build your own from source (requires [node.js](http://nodejs.org/download/), [grunt](http://gruntjs.com/getting-started#installing-the-cli) and [sass](http://sass-lang.com/install)):
|
You can download a prebuilt bundle under [releases](https://github.com/whiteout-io/mail/releases) or build your own from source (requires [node.js](http://nodejs.org/download/), [grunt](http://gruntjs.com/getting-started#installing-the-cli) and [sass](http://sass-lang.com/install)):
|
||||||
|
|
||||||
npm install && npm test
|
npm install && npm test
|
||||||
|
|
||||||
@ -69,7 +71,7 @@ The App can be used either as a Chrome Packaged App or just by hosting it on you
|
|||||||
|
|
||||||
Clone the git repository
|
Clone the git repository
|
||||||
|
|
||||||
git clone https://github.com/whiteout-io/mail-html5.git
|
git clone https://github.com/whiteout-io/mail.git
|
||||||
|
|
||||||
Build and generate the `dist/` directory:
|
Build and generate the `dist/` directory:
|
||||||
|
|
||||||
|
22
package.json
22
package.json
@ -3,21 +3,6 @@
|
|||||||
"description": "Mail App with integrated OpenPGP encryption.",
|
"description": "Mail App with integrated OpenPGP encryption.",
|
||||||
"author": "Whiteout Networks",
|
"author": "Whiteout Networks",
|
||||||
"homepage": "https://whiteout.io",
|
"homepage": "https://whiteout.io",
|
||||||
"repository": {
|
|
||||||
"type": "git",
|
|
||||||
"url": "https://github.com/whiteout-io/mail-html5.git"
|
|
||||||
},
|
|
||||||
"keywords": [
|
|
||||||
"email",
|
|
||||||
"mail",
|
|
||||||
"client",
|
|
||||||
"app",
|
|
||||||
"openpgp",
|
|
||||||
"pgp",
|
|
||||||
"gpg",
|
|
||||||
"imap",
|
|
||||||
"smtp"
|
|
||||||
],
|
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=0.10"
|
"node": ">=0.10"
|
||||||
},
|
},
|
||||||
@ -40,11 +25,12 @@
|
|||||||
"browsersmtp": "https://github.com/whiteout-io/browsersmtp/tarball/master",
|
"browsersmtp": "https://github.com/whiteout-io/browsersmtp/tarball/master",
|
||||||
"chai": "~1.9.2",
|
"chai": "~1.9.2",
|
||||||
"crypto-lib": "~0.2.1",
|
"crypto-lib": "~0.2.1",
|
||||||
"dompurify": "~0.6.3",
|
"dompurify": "~0.7.3",
|
||||||
"grunt": "~0.4.1",
|
"grunt": "~0.4.1",
|
||||||
"grunt-angular-templates": "~0.5.7",
|
"grunt-angular-templates": "~0.5.7",
|
||||||
"grunt-autoprefixer": "~0.7.2",
|
"grunt-autoprefixer": "~0.7.2",
|
||||||
"grunt-browserify": "^3.0.1",
|
"grunt-browserify": "3.7.0",
|
||||||
|
"insert-module-globals": "6.5.0",
|
||||||
"grunt-contrib-clean": "~0.5.0",
|
"grunt-contrib-clean": "~0.5.0",
|
||||||
"grunt-contrib-compress": "~0.5.2",
|
"grunt-contrib-compress": "~0.5.2",
|
||||||
"grunt-contrib-concat": "^0.5.0",
|
"grunt-contrib-concat": "^0.5.0",
|
||||||
@ -57,7 +43,7 @@
|
|||||||
"grunt-csso": "~0.6.1",
|
"grunt-csso": "~0.6.1",
|
||||||
"grunt-exorcise": "^0.2.0",
|
"grunt-exorcise": "^0.2.0",
|
||||||
"grunt-manifest": "^0.4.0",
|
"grunt-manifest": "^0.4.0",
|
||||||
"grunt-mocha-phantomjs": "^0.6.0",
|
"grunt-mocha-phantomjs": "^0.7.0",
|
||||||
"grunt-shell": "~1.1.1",
|
"grunt-shell": "~1.1.1",
|
||||||
"grunt-string-replace": "~1.0.0",
|
"grunt-string-replace": "~1.0.0",
|
||||||
"grunt-svgmin": "~1.0.0",
|
"grunt-svgmin": "~1.0.0",
|
||||||
|
@ -99,7 +99,7 @@ appCfg.string = {
|
|||||||
certificateFaqLink: 'https://github.com/whiteout-io/mail-html5/wiki/FAQ#what-does-the-ssl-certificate-for-the-mail-server--changed-mean',
|
certificateFaqLink: 'https://github.com/whiteout-io/mail-html5/wiki/FAQ#what-does-the-ssl-certificate-for-the-mail-server--changed-mean',
|
||||||
bugReportTitle: 'Report a bug',
|
bugReportTitle: 'Report a bug',
|
||||||
bugReportSubject: '[Bug] I want to report a bug',
|
bugReportSubject: '[Bug] I want to report a bug',
|
||||||
bugReportBody: 'Steps to reproduce\n1. \n2. \n3. \n\nWhat happens?\n\n\nWhat do you expect to happen instead?\n\n\n\n== PLEASE DONT PUT ANY KEYS HERE! ==\n\n\n## Log\n\nBelow is the log. It includes your interactions with your email provider in an anonymized way from the point where you started the app for the last time. Any information provided by you will be used for the porpose of locating and fixing the bug you reported. It will be deleted subsequently. However, you can edit this log and/or remove log data in the event that something would show up.\n\nUser-Agent: {0}\nVersion: {1}\n\n',
|
bugReportBody: 'Steps to reproduce\n1. \n2. \n3. \n\nWhat happens?\n\n\nWhat do you expect to happen instead?\n\n\n\n== PLEASE DONT PUT ANY KEYS HERE! ==\n\n\n## Log\n\nBelow is the log. It includes your interactions with your email provider from the point where you started the app for the last time. Login data and email content has been stripped. Any information provided by you will be used for the purpose of locating and fixing the bug you reported. It will be deleted subsequently. However, you can edit this log and/or remove log data in the event that something would show up.\n\nUser-Agent: {0}\nVersion: {1}\n\n',
|
||||||
supportAddress: 'mail.support@whiteout.io',
|
supportAddress: 'mail.support@whiteout.io',
|
||||||
connDocOffline: 'It appears that you are offline. Please retry when you are online.',
|
connDocOffline: 'It appears that you are offline. Please retry when you are online.',
|
||||||
connDocTlsWrongCert: 'A connection to {0} was rejected because the TLS certificate is invalid. Please have a look at the FAQ for information on how to fix this error.',
|
connDocTlsWrongCert: 'A connection to {0} was rejected because the TLS certificate is invalid. Please have a look at the FAQ for information on how to fix this error.',
|
||||||
@ -109,5 +109,7 @@ appCfg.string = {
|
|||||||
connDocNoInbox: 'We could not detect an IMAP inbox folder on {0}. Please have a look at the FAQ for information on how to fix this error.',
|
connDocNoInbox: 'We could not detect an IMAP inbox folder on {0}. Please have a look at the FAQ for information on how to fix this error.',
|
||||||
connDocGenericError: 'There was an error connecting to {0}: {1}',
|
connDocGenericError: 'There was an error connecting to {0}: {1}',
|
||||||
logoutTitle: 'Logout',
|
logoutTitle: 'Logout',
|
||||||
logoutMessage: 'Are you sure you want to log out? Please back up your encryption key before proceeding!'
|
logoutMessage: 'Are you sure you want to log out? Please back up your encryption key before proceeding!',
|
||||||
};
|
removePreAuthAccountTitle: 'Remove account',
|
||||||
|
removePreAuthAccountMessage: 'Are you sure you want to remove your account from this device?'
|
||||||
|
};
|
@ -8,6 +8,42 @@ var ActionBarCtrl = function($scope, $q, email, dialog, status) {
|
|||||||
// scope functions
|
// scope functions
|
||||||
//
|
//
|
||||||
|
|
||||||
|
$scope.CHECKNONE = 0;
|
||||||
|
$scope.CHECKALL = 1;
|
||||||
|
$scope.CHECKUNREAD = 2;
|
||||||
|
$scope.CHECKREAD = 3;
|
||||||
|
$scope.CHECKFLAGGED = 4;
|
||||||
|
$scope.CHECKUNFLAGGED = 5;
|
||||||
|
$scope.CHECKENCRYPTED = 6;
|
||||||
|
$scope.CHECKUNENCRYPTED = 7;
|
||||||
|
|
||||||
|
$scope.check = function(option) {
|
||||||
|
currentFolder().messages.forEach(function(email) {
|
||||||
|
if (!email.from) {
|
||||||
|
// only mark loaded messages, not the dummy messages
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (option === $scope.CHECKNONE) {
|
||||||
|
email.checked = false;
|
||||||
|
} else if (option === $scope.CHECKALL) {
|
||||||
|
email.checked = true;
|
||||||
|
} else if (option === $scope.CHECKUNREAD) {
|
||||||
|
email.checked = !!email.unread;
|
||||||
|
} else if (option === $scope.CHECKREAD) {
|
||||||
|
email.checked = !email.unread;
|
||||||
|
} else if (option === $scope.CHECKFLAGGED) {
|
||||||
|
email.checked = !!email.flagged;
|
||||||
|
} else if (option === $scope.CHECKUNFLAGGED) {
|
||||||
|
email.checked = !email.flagged;
|
||||||
|
} else if (option === $scope.CHECKENCRYPTED) {
|
||||||
|
email.checked = !!email.encrypted;
|
||||||
|
} else if (option === $scope.CHECKUNENCRYPTED) {
|
||||||
|
email.checked = !email.encrypted;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Move a single message from the currently selected folder to another folder
|
* Move a single message from the currently selected folder to another folder
|
||||||
* @param {Object} message The message that is to be moved
|
* @param {Object} message The message that is to be moved
|
||||||
|
@ -52,8 +52,7 @@ var CreateAccountCtrl = function($scope, $location, $routeParams, $q, auth, admi
|
|||||||
return admin.createUser({
|
return admin.createUser({
|
||||||
emailAddress: emailAddress,
|
emailAddress: emailAddress,
|
||||||
password: $scope.pass,
|
password: $scope.pass,
|
||||||
phone: phone.internationalNumber,
|
phone: phone.internationalNumber
|
||||||
betaCode: $scope.betaCode.toUpperCase()
|
|
||||||
});
|
});
|
||||||
|
|
||||||
}).then(function() {
|
}).then(function() {
|
||||||
|
@ -1,8 +1,10 @@
|
|||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
var LoginExistingCtrl = function($scope, $location, $routeParams, $q, email, auth, keychain) {
|
var LoginExistingCtrl = function($scope, $location, $routeParams, $q, email, auth, keychain, account, dialog, appConfig) {
|
||||||
!$routeParams.dev && !auth.isInitialized() && $location.path('/'); // init app
|
!$routeParams.dev && !auth.isInitialized() && $location.path('/'); // init app
|
||||||
|
|
||||||
|
var str = appConfig.string;
|
||||||
|
|
||||||
$scope.confirmPassphrase = function() {
|
$scope.confirmPassphrase = function() {
|
||||||
if ($scope.form.$invalid) {
|
if ($scope.form.$invalid) {
|
||||||
$scope.errMsg = 'Please fill out all required fields!';
|
$scope.errMsg = 'Please fill out all required fields!';
|
||||||
@ -38,6 +40,18 @@ var LoginExistingCtrl = function($scope, $location, $routeParams, $q, email, aut
|
|||||||
}).catch(displayError);
|
}).catch(displayError);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
$scope.logout = function() {
|
||||||
|
return dialog.confirm({
|
||||||
|
title: str.removePreAuthAccountTitle,
|
||||||
|
message: str.removePreAuthAccountMessage,
|
||||||
|
callback: function(confirm) {
|
||||||
|
if (confirm) {
|
||||||
|
account.logout().catch(dialog.error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
function displayError(err) {
|
function displayError(err) {
|
||||||
$scope.busy = false;
|
$scope.busy = false;
|
||||||
$scope.incorrect = true;
|
$scope.incorrect = true;
|
||||||
|
@ -106,7 +106,7 @@ PGP.prototype.getKeyId = function(keyArmored) {
|
|||||||
* Read all relevant params of an armored key.
|
* Read all relevant params of an armored key.
|
||||||
*/
|
*/
|
||||||
PGP.prototype.getKeyParams = function(keyArmored) {
|
PGP.prototype.getKeyParams = function(keyArmored) {
|
||||||
var key, packet, userIds;
|
var key, packet, userIds, emailAddress;
|
||||||
|
|
||||||
// process armored key input
|
// process armored key input
|
||||||
if (keyArmored) {
|
if (keyArmored) {
|
||||||
@ -122,15 +122,24 @@ PGP.prototype.getKeyParams = function(keyArmored) {
|
|||||||
// read user names and email addresses
|
// read user names and email addresses
|
||||||
userIds = [];
|
userIds = [];
|
||||||
key.getUserIds().forEach(function(userId) {
|
key.getUserIds().forEach(function(userId) {
|
||||||
|
if (!userId || userId.indexOf('<') < 0 || userId.indexOf('>') < 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
userIds.push({
|
userIds.push({
|
||||||
name: userId.split('<')[0].trim(),
|
name: userId.split('<')[0].trim(),
|
||||||
emailAddress: userId.split('<')[1].split('>')[0].trim()
|
emailAddress: userId.split('<')[1].split('>')[0].trim()
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// check user ID
|
||||||
|
emailAddress = userIds[0] && userIds[0].emailAddress;
|
||||||
|
if (!emailAddress) {
|
||||||
|
throw new Error('Cannot parse PGP key user ID!');
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
_id: packet.getKeyId().toHex().toUpperCase(),
|
_id: packet.getKeyId().toHex().toUpperCase(),
|
||||||
userId: userIds[0].emailAddress, // the primary (first) email address of the key
|
userId: emailAddress, // the primary (first) email address of the key
|
||||||
userIds: userIds, // a dictonary of all the key's name/address pairs
|
userIds: userIds, // a dictonary of all the key's name/address pairs
|
||||||
fingerprint: packet.getFingerprint().toUpperCase(),
|
fingerprint: packet.getFingerprint().toUpperCase(),
|
||||||
algorithm: packet.algorithm,
|
algorithm: packet.algorithm,
|
||||||
|
@ -8,6 +8,7 @@ ngModule.directive('keyfileInput', function() {
|
|||||||
for (var i = 0; i < e.target.files.length; i++) {
|
for (var i = 0; i < e.target.files.length; i++) {
|
||||||
importKey(e.target.files.item(i));
|
importKey(e.target.files.item(i));
|
||||||
}
|
}
|
||||||
|
elm.val(null); // clear input
|
||||||
});
|
});
|
||||||
|
|
||||||
function importKey(file) {
|
function importKey(file) {
|
||||||
|
@ -938,8 +938,10 @@ Email.prototype.onConnect = function(imap) {
|
|||||||
Email.prototype.onDisconnect = function() {
|
Email.prototype.onDisconnect = function() {
|
||||||
// logout of imap-client
|
// logout of imap-client
|
||||||
// ignore error, because it's not problem if logout fails
|
// ignore error, because it's not problem if logout fails
|
||||||
this._imapClient.stopListeningForChanges(function() {});
|
if (this._imapClient) {
|
||||||
this._imapClient.logout(function() {});
|
this._imapClient.stopListeningForChanges(function() {});
|
||||||
|
this._imapClient.logout(function() {});
|
||||||
|
}
|
||||||
|
|
||||||
// discard clients
|
// discard clients
|
||||||
this._account.online = false;
|
this._account.online = false;
|
||||||
@ -1181,7 +1183,7 @@ Email.prototype._updateFolders = function() {
|
|||||||
return 1;
|
return 1;
|
||||||
} else {
|
} else {
|
||||||
// non-wellknown folders should be sorted case-insensitive
|
// non-wellknown folders should be sorted case-insensitive
|
||||||
return a.name.toLowerCase().localeCompare(b.name.toLowerCase());
|
return a.path.toLowerCase().localeCompare(b.path.toLowerCase());
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -1593,7 +1595,7 @@ Email.prototype._extractBody = function(message) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// any content before/after the PGP block will be discarded, untrusted attachments and html is ignored
|
// any content before/after the PGP block will be discarded, untrusted attachments and html is ignored
|
||||||
var clearSignedMatch = /^-{5}BEGIN PGP SIGNED MESSAGE-{5}\nHash:[ ][^\n]+\n(?:[A-Za-z]+:[ ][^\n]+\n)*\n([\s\S]*)\n-{5}BEGIN PGP SIGNATURE-{5}[\S\s]*-{5}END PGP SIGNATURE-{5}$/im.exec(body);
|
var clearSignedMatch = /^-{5}BEGIN PGP SIGNED MESSAGE-{5}\nHash:[ ][^\n]+\n(?:[A-Za-z]+:[ ][^\n]+\n)*\n([\s\S]*?)\n-{5}BEGIN PGP SIGNATURE-{5}[\S\s]*-{5}END PGP SIGNATURE-{5}$/im.exec(body);
|
||||||
if (clearSignedMatch) {
|
if (clearSignedMatch) {
|
||||||
// PGP/INLINE signed
|
// PGP/INLINE signed
|
||||||
message.signed = true;
|
message.signed = true;
|
||||||
|
@ -102,24 +102,39 @@ PrivateKey.prototype.upload = function(options) {
|
|||||||
|
|
||||||
return new Promise(function(resolve) {
|
return new Promise(function(resolve) {
|
||||||
if (!options._id || !options.userId || !options.encryptedPrivateKey || !options.salt || !options.iv) {
|
if (!options._id || !options.userId || !options.encryptedPrivateKey || !options.salt || !options.iv) {
|
||||||
throw new Error('Incomplete arguments!');
|
throw new Error('Incomplete arguments for key upload!');
|
||||||
}
|
}
|
||||||
resolve();
|
resolve();
|
||||||
|
|
||||||
}).then(function() {
|
}).then(function() {
|
||||||
// create imap folder
|
|
||||||
return self._imap.createFolder({
|
// Some servers (Exchange, Cyrus) error when creating an existing IMAP mailbox instead of
|
||||||
path: IMAP_KEYS_FOLDER
|
// responding with ALREADYEXISTS. Hence we search for the folder before uploading.
|
||||||
}).then(function(fullPath) {
|
|
||||||
|
self._axe.debug('Searching imap folder for key upload...');
|
||||||
|
|
||||||
|
return self._getFolder().then(function(fullPath) {
|
||||||
path = fullPath;
|
path = fullPath;
|
||||||
self._axe.debug('Successfully created imap folder ' + path);
|
}).catch(function() {
|
||||||
}).catch(function(err) {
|
|
||||||
var prettyErr = new Error('Creating imap folder ' + IMAP_KEYS_FOLDER + ' failed: ' + err.message);
|
// create imap folder
|
||||||
self._axe.error(prettyErr);
|
self._axe.debug('Folder not found, creating imap folder.');
|
||||||
throw prettyErr;
|
return self._imap.createFolder({
|
||||||
|
path: IMAP_KEYS_FOLDER
|
||||||
|
}).then(function(fullPath) {
|
||||||
|
path = fullPath;
|
||||||
|
self._axe.debug('Successfully created imap folder ' + path);
|
||||||
|
}).catch(function(err) {
|
||||||
|
var prettyErr = new Error('Creating imap folder ' + IMAP_KEYS_FOLDER + ' failed: ' + err.message);
|
||||||
|
self._axe.error(prettyErr);
|
||||||
|
throw prettyErr;
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
}).then(createMessage).then(function(message) {
|
}).then(createMessage).then(function(message) {
|
||||||
|
|
||||||
// upload to imap folder
|
// upload to imap folder
|
||||||
|
self._axe.debug('Uploading key...');
|
||||||
return self._imap.uploadMessage({
|
return self._imap.uploadMessage({
|
||||||
path: path,
|
path: path,
|
||||||
message: message
|
message: message
|
||||||
@ -165,9 +180,13 @@ PrivateKey.prototype.upload = function(options) {
|
|||||||
* Check if matching private key is stored in IMAP.
|
* Check if matching private key is stored in IMAP.
|
||||||
*/
|
*/
|
||||||
PrivateKey.prototype.isSynced = function() {
|
PrivateKey.prototype.isSynced = function() {
|
||||||
return this._fetchMessage({
|
var self = this;
|
||||||
userId: this._auth.emailAddress,
|
|
||||||
keyId: this._pgp.getKeyId()
|
return self._getFolder().then(function(path) {
|
||||||
|
return self._fetchMessage({
|
||||||
|
keyId: self._pgp.getKeyId(),
|
||||||
|
path: path
|
||||||
|
});
|
||||||
}).then(function(msg) {
|
}).then(function(msg) {
|
||||||
return !!msg;
|
return !!msg;
|
||||||
}).catch(function() {
|
}).catch(function() {
|
||||||
@ -183,18 +202,24 @@ PrivateKey.prototype.isSynced = function() {
|
|||||||
*/
|
*/
|
||||||
PrivateKey.prototype.download = function(options) {
|
PrivateKey.prototype.download = function(options) {
|
||||||
var self = this,
|
var self = this,
|
||||||
message;
|
path, message;
|
||||||
|
|
||||||
return self._fetchMessage(options).then(function(msg) {
|
return self._getFolder().then(function(fullPath) {
|
||||||
if (!msg) {
|
path = fullPath;
|
||||||
throw new Error('Private key not synced!');
|
return self._fetchMessage({
|
||||||
}
|
keyId: options.keyId,
|
||||||
|
path: path
|
||||||
|
}).then(function(msg) {
|
||||||
|
if (!msg) {
|
||||||
|
throw new Error('Private key not synced!');
|
||||||
|
}
|
||||||
|
|
||||||
message = msg;
|
message = msg;
|
||||||
|
});
|
||||||
}).then(function() {
|
}).then(function() {
|
||||||
// get the body for the message
|
// get the body for the message
|
||||||
return self._imap.getBodyParts({
|
return self._imap.getBodyParts({
|
||||||
path: IMAP_KEYS_FOLDER,
|
path: path,
|
||||||
uid: message.uid,
|
uid: message.uid,
|
||||||
bodyParts: message.bodyParts
|
bodyParts: message.bodyParts
|
||||||
});
|
});
|
||||||
@ -282,17 +307,9 @@ PrivateKey.prototype.decrypt = function(options) {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
PrivateKey.prototype._fetchMessage = function(options) {
|
PrivateKey.prototype._getFolder = function() {
|
||||||
var self = this;
|
var self = this;
|
||||||
|
|
||||||
if (!options.userId || !options.keyId) {
|
|
||||||
return new Promise(function() {
|
|
||||||
throw new Error('Incomplete arguments!');
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// get the metadata for the message
|
|
||||||
|
|
||||||
return self._imap.listWellKnownFolders().then(function(wellKnownFolders) {
|
return self._imap.listWellKnownFolders().then(function(wellKnownFolders) {
|
||||||
var paths = []; // gathers paths
|
var paths = []; // gathers paths
|
||||||
|
|
||||||
@ -318,12 +335,21 @@ PrivateKey.prototype._fetchMessage = function(options) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return paths[0];
|
return paths[0];
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
}).then(function(path) {
|
PrivateKey.prototype._fetchMessage = function(options) {
|
||||||
return self._imap.listMessages({
|
var self = this;
|
||||||
path: path,
|
|
||||||
|
if (!options.keyId) {
|
||||||
|
return new Promise(function() {
|
||||||
|
throw new Error('Incomplete arguments!');
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// get the metadata for the message
|
||||||
|
return self._imap.listMessages({
|
||||||
|
path: options.path
|
||||||
}).then(function(messages) {
|
}).then(function(messages) {
|
||||||
if (!messages.length) {
|
if (!messages.length) {
|
||||||
// message has been deleted in the meantime
|
// message has been deleted in the meantime
|
||||||
|
@ -25,10 +25,10 @@ Download.prototype.createDownload = function(options) {
|
|||||||
supportsBlob = !!new Blob();
|
supportsBlob = !!new Blob();
|
||||||
} catch (e) {}
|
} catch (e) {}
|
||||||
|
|
||||||
if (typeof a.download !== "undefined" && supportsBlob) {
|
if (typeof a.download !== 'undefined' && supportsBlob) {
|
||||||
// ff 30+, chrome 27+ (android: 37+)
|
// ff 30+, chrome 27+ (android: 37+)
|
||||||
document.body.appendChild(a);
|
document.body.appendChild(a);
|
||||||
a.style = "display: none";
|
a.style.display = 'none';
|
||||||
a.href = window.URL.createObjectURL(new Blob([content], {
|
a.href = window.URL.createObjectURL(new Blob([content], {
|
||||||
type: contentType
|
type: contentType
|
||||||
}));
|
}));
|
||||||
@ -52,15 +52,15 @@ Download.prototype.createDownload = function(options) {
|
|||||||
var url = window.URL.createObjectURL(new Blob([content], {
|
var url = window.URL.createObjectURL(new Blob([content], {
|
||||||
type: contentType
|
type: contentType
|
||||||
}));
|
}));
|
||||||
var newTab = window.open(url, "_blank");
|
var newTab = window.open(url, '_blank');
|
||||||
if (!newTab) {
|
if (!newTab) {
|
||||||
window.location.href = url;
|
window.location.href = url;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// anything else, where anything at all is better than nothing
|
// anything else, where anything at all is better than nothing
|
||||||
if (typeof content !== "string" && content.buffer) {
|
if (typeof content !== 'string' && content.buffer) {
|
||||||
content = util.arrBuf2BinStr(content.buffer);
|
content = util.arrBuf2BinStr(content.buffer);
|
||||||
}
|
}
|
||||||
window.open('data:' + contentType + ';base64,' + btoa(content), "_blank");
|
window.open('data:' + contentType + ';base64,' + btoa(content), '_blank');
|
||||||
}
|
}
|
||||||
};
|
};
|
@ -1,174 +0,0 @@
|
|||||||
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
|
||||||
// JavaScript Dynamic Content shim for Windows Store apps
|
|
||||||
(function () {
|
|
||||||
|
|
||||||
if (window.MSApp && MSApp.execUnsafeLocalFunction) {
|
|
||||||
|
|
||||||
// Some nodes will have an "attributes" property which shadows the Node.prototype.attributes property
|
|
||||||
// and means we don't actually see the attributes of the Node (interestingly the VS debug console
|
|
||||||
// appears to suffer from the same issue).
|
|
||||||
//
|
|
||||||
var Element_setAttribute = Object.getOwnPropertyDescriptor(Element.prototype, "setAttribute").value;
|
|
||||||
var Element_removeAttribute = Object.getOwnPropertyDescriptor(Element.prototype, "removeAttribute").value;
|
|
||||||
var HTMLElement_insertAdjacentHTMLPropertyDescriptor = Object.getOwnPropertyDescriptor(HTMLElement.prototype, "insertAdjacentHTML");
|
|
||||||
var Node_get_attributes = Object.getOwnPropertyDescriptor(Node.prototype, "attributes").get;
|
|
||||||
var Node_get_childNodes = Object.getOwnPropertyDescriptor(Node.prototype, "childNodes").get;
|
|
||||||
var detectionDiv = document.createElement("div");
|
|
||||||
|
|
||||||
function getAttributes(element) {
|
|
||||||
return Node_get_attributes.call(element);
|
|
||||||
}
|
|
||||||
|
|
||||||
function setAttribute(element, attribute, value) {
|
|
||||||
try {
|
|
||||||
Element_setAttribute.call(element, attribute, value);
|
|
||||||
} catch (e) {
|
|
||||||
// ignore
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function removeAttribute(element, attribute) {
|
|
||||||
Element_removeAttribute.call(element, attribute);
|
|
||||||
}
|
|
||||||
|
|
||||||
function childNodes(element) {
|
|
||||||
return Node_get_childNodes.call(element);
|
|
||||||
}
|
|
||||||
|
|
||||||
function empty(element) {
|
|
||||||
while (element.childNodes.length) {
|
|
||||||
element.removeChild(element.lastChild);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function insertAdjacentHTML(element, position, html) {
|
|
||||||
HTMLElement_insertAdjacentHTMLPropertyDescriptor.value.call(element, position, html);
|
|
||||||
}
|
|
||||||
|
|
||||||
function inUnsafeMode() {
|
|
||||||
var isUnsafe = true;
|
|
||||||
try {
|
|
||||||
detectionDiv.innerHTML = "<test/>";
|
|
||||||
}
|
|
||||||
catch (ex) {
|
|
||||||
isUnsafe = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return isUnsafe;
|
|
||||||
}
|
|
||||||
|
|
||||||
function cleanse(html, targetElement) {
|
|
||||||
var cleaner = document.implementation.createHTMLDocument("cleaner");
|
|
||||||
empty(cleaner.documentElement);
|
|
||||||
MSApp.execUnsafeLocalFunction(function () {
|
|
||||||
insertAdjacentHTML(cleaner.documentElement, "afterbegin", html);
|
|
||||||
});
|
|
||||||
|
|
||||||
var scripts = cleaner.documentElement.querySelectorAll("script");
|
|
||||||
Array.prototype.forEach.call(scripts, function (script) {
|
|
||||||
switch (script.type.toLowerCase()) {
|
|
||||||
case "":
|
|
||||||
script.type = "text/inert";
|
|
||||||
break;
|
|
||||||
case "text/javascript":
|
|
||||||
case "text/ecmascript":
|
|
||||||
case "text/x-javascript":
|
|
||||||
case "text/jscript":
|
|
||||||
case "text/livescript":
|
|
||||||
case "text/javascript1.1":
|
|
||||||
case "text/javascript1.2":
|
|
||||||
case "text/javascript1.3":
|
|
||||||
script.type = "text/inert-" + script.type.slice("text/".length);
|
|
||||||
break;
|
|
||||||
case "application/javascript":
|
|
||||||
case "application/ecmascript":
|
|
||||||
case "application/x-javascript":
|
|
||||||
script.type = "application/inert-" + script.type.slice("application/".length);
|
|
||||||
break;
|
|
||||||
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
function cleanseAttributes(element) {
|
|
||||||
var attributes = getAttributes(element);
|
|
||||||
if (attributes && attributes.length) {
|
|
||||||
// because the attributes collection is live it is simpler to queue up the renames
|
|
||||||
var events;
|
|
||||||
for (var i = 0, len = attributes.length; i < len; i++) {
|
|
||||||
var attribute = attributes[i];
|
|
||||||
var name = attribute.name;
|
|
||||||
if ((name[0] === "o" || name[0] === "O") &&
|
|
||||||
(name[1] === "n" || name[1] === "N")) {
|
|
||||||
events = events || [];
|
|
||||||
events.push({ name: attribute.name, value: attribute.value });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (events) {
|
|
||||||
for (var i = 0, len = events.length; i < len; i++) {
|
|
||||||
var attribute = events[i];
|
|
||||||
removeAttribute(element, attribute.name);
|
|
||||||
setAttribute(element, "x-" + attribute.name, attribute.value);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
var children = childNodes(element);
|
|
||||||
for (var i = 0, len = children.length; i < len; i++) {
|
|
||||||
cleanseAttributes(children[i]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
cleanseAttributes(cleaner.documentElement);
|
|
||||||
|
|
||||||
var cleanedNodes = [];
|
|
||||||
|
|
||||||
if (targetElement.tagName === 'HTML') {
|
|
||||||
cleanedNodes = Array.prototype.slice.call(document.adoptNode(cleaner.documentElement).childNodes);
|
|
||||||
} else {
|
|
||||||
if (cleaner.head) {
|
|
||||||
cleanedNodes = cleanedNodes.concat(Array.prototype.slice.call(document.adoptNode(cleaner.head).childNodes));
|
|
||||||
}
|
|
||||||
if (cleaner.body) {
|
|
||||||
cleanedNodes = cleanedNodes.concat(Array.prototype.slice.call(document.adoptNode(cleaner.body).childNodes));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return cleanedNodes;
|
|
||||||
}
|
|
||||||
|
|
||||||
function cleansePropertySetter(property, setter) {
|
|
||||||
var propertyDescriptor = Object.getOwnPropertyDescriptor(HTMLElement.prototype, property);
|
|
||||||
var originalSetter = propertyDescriptor.set;
|
|
||||||
Object.defineProperty(HTMLElement.prototype, property, {
|
|
||||||
get: propertyDescriptor.get,
|
|
||||||
set: function (value) {
|
|
||||||
if(window.WinJS && window.WinJS._execUnsafe && inUnsafeMode()) {
|
|
||||||
originalSetter.call(this, value);
|
|
||||||
} else {
|
|
||||||
var that = this;
|
|
||||||
var nodes = cleanse(value, that);
|
|
||||||
MSApp.execUnsafeLocalFunction(function () {
|
|
||||||
setter(propertyDescriptor, that, nodes);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
},
|
|
||||||
enumerable: propertyDescriptor.enumerable,
|
|
||||||
configurable: propertyDescriptor.configurable,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
cleansePropertySetter("innerHTML", function (propertyDescriptor, target, elements) {
|
|
||||||
empty(target);
|
|
||||||
for (var i = 0, len = elements.length; i < len; i++) {
|
|
||||||
target.appendChild(elements[i]);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
cleansePropertySetter("outerHTML", function (propertyDescriptor, target, elements) {
|
|
||||||
for (var i = 0, len = elements.length; i < len; i++) {
|
|
||||||
target.insertAdjacentElement("afterend", elements[i]);
|
|
||||||
}
|
|
||||||
target.parentNode.removeChild(target);
|
|
||||||
});
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}());
|
|
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"packageId": "io.whiteout.WhiteoutMail",
|
"packageId": "io.whiteout.WhiteoutMail",
|
||||||
"versionCode": 24,
|
"versionCode": 28,
|
||||||
"CFBundleVersion": "1",
|
"CFBundleVersion": "1",
|
||||||
|
|
||||||
"ios": {
|
"ios": {
|
||||||
|
@ -191,6 +191,9 @@
|
|||||||
.mail-addresses__stripped {
|
.mail-addresses__stripped {
|
||||||
display: inline;
|
display: inline;
|
||||||
}
|
}
|
||||||
|
.read__sender-address {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -1,5 +1,9 @@
|
|||||||
<div class="action-bar" ng-controller="ActionBarCtrl">
|
<div class="action-bar" ng-controller="ActionBarCtrl">
|
||||||
<div class="action-bar__primary">
|
<div class="action-bar__primary">
|
||||||
|
<button class="btn btn--light-dropdown" ng-hide="state.read.open" wo-dropdown="#dropdown-checkmessages">
|
||||||
|
<svg role="presentation"><use xlink:href="#icon-check" />Check messages</svg>
|
||||||
|
<svg class="btn__dropdown" role="presentation"><use xlink:href="#icon-dropdown" /></svg>
|
||||||
|
</button>
|
||||||
<button class="btn btn--light" wo-touch="state.read.open ? deleteMessage(state.mailList.selected) : deleteCheckedMessages()">Delete</button>
|
<button class="btn btn--light" wo-touch="state.read.open ? deleteMessage(state.mailList.selected) : deleteCheckedMessages()">Delete</button>
|
||||||
<button class="btn btn--light" wo-touch="state.read.open ? moveMessage(state.mailList.selected, getJunkFolder()) : moveCheckedMessages(getJunkFolder())">Spam</button>
|
<button class="btn btn--light" wo-touch="state.read.open ? moveMessage(state.mailList.selected, getJunkFolder()) : moveCheckedMessages(getJunkFolder())">Spam</button>
|
||||||
<button class="btn btn--light-dropdown" wo-dropdown="#dropdown-folder">
|
<button class="btn btn--light-dropdown" wo-dropdown="#dropdown-folder">
|
||||||
@ -34,6 +38,17 @@
|
|||||||
</li>
|
</li>
|
||||||
</ul><!--/dropdown-->
|
</ul><!--/dropdown-->
|
||||||
|
|
||||||
|
<ul id="dropdown-checkmessages" class="dropdown">
|
||||||
|
<li><button wo-touch="check(CHECKALL)">All</button></li>
|
||||||
|
<li><button wo-touch="check(CHECKNONE)">None</button></li>
|
||||||
|
<li><button wo-touch="check(CHECKENCRYPTED)">Encrypted</button></li>
|
||||||
|
<li><button wo-touch="check(CHECKUNENCRYPTED)">Unencrypted</button></li>
|
||||||
|
<li><button wo-touch="check(CHECKUNREAD)">Unread</button></li>
|
||||||
|
<li><button wo-touch="check(CHECKREAD)">Read</button></li>
|
||||||
|
<li><button wo-touch="check(CHECKFLAGGED)">Starred</button></li>
|
||||||
|
<li><button wo-touch="check(CHECKUNFLAGGED)">Unstarred</button></li>
|
||||||
|
</ul><!--/checkmessages-->
|
||||||
|
|
||||||
<ul id="dropdown-more" class="dropdown">
|
<ul id="dropdown-more" class="dropdown">
|
||||||
<li><button wo-touch="state.read.open ? markMessage(state.mailList.selected, false) : markCheckedMessages(false)">Mark as read</button></li>
|
<li><button wo-touch="state.read.open ? markMessage(state.mailList.selected, false) : markCheckedMessages(false)">Mark as read</button></li>
|
||||||
<li><button wo-touch="state.read.open ? markMessage(state.mailList.selected, true) : markCheckedMessages(true)">Mark as unread</button></li>
|
<li><button wo-touch="state.read.open ? markMessage(state.mailList.selected, true) : markCheckedMessages(true)">Mark as unread</button></li>
|
||||||
|
@ -287,9 +287,6 @@
|
|||||||
<input class="input-text" type="tel" ng-model="dial" required placeholder="Mobile phone (for SMS validation)" tabindex="5">
|
<input class="input-text" type="tel" ng-model="dial" required placeholder="Mobile phone (for SMS validation)" tabindex="5">
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="form__row">
|
|
||||||
<input class="input-text" type="text" ng-model="betaCode" required placeholder="Invitation code" tabindex="6">
|
|
||||||
</div>
|
|
||||||
<div class="spinner-block" ng-show="busy">
|
<div class="spinner-block" ng-show="busy">
|
||||||
<span class="spinner spinner--big"></span>
|
<span class="spinner spinner--big"></span>
|
||||||
</div>
|
</div>
|
||||||
@ -297,9 +294,6 @@
|
|||||||
<button class="btn" type="submit" ng-click="showConfirm()">Create</button>
|
<button class="btn" type="submit" ng-click="showConfirm()">Create</button>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
<p class="typo-paragraph">
|
|
||||||
<a href="http://eepurl.com/ba09in" target="_blank" title="Sign up for mailbox access.">Need an invitation code?</a>
|
|
||||||
</p>
|
|
||||||
</main>
|
</main>
|
||||||
<div ng-include="'tpl/page-footer.html'"></div>
|
<div ng-include="'tpl/page-footer.html'"></div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -25,7 +25,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
<p class="typo-paragraph">
|
<p class="typo-paragraph">
|
||||||
<a href="https://whiteout.io/revocation.html" title="Click here to reset your account." target="_blank">Forgot your passphrase?</a>
|
<a href="#" wo-touch="$event.preventDefault(); logout()" title="Remove account from device">Forgot your passphrase?</a>
|
||||||
</p>
|
</p>
|
||||||
</main>
|
</main>
|
||||||
<div ng-include="'tpl/page-footer.html'"></div>
|
<div ng-include="'tpl/page-footer.html'"></div>
|
||||||
|
@ -75,7 +75,7 @@
|
|||||||
<label>From:</label>
|
<label>From:</label>
|
||||||
<span ng-repeat="u in state.mailList.selected.from">
|
<span ng-repeat="u in state.mailList.selected.from">
|
||||||
<span class="label" ng-class="{'label--invalid': u.secure === false, 'label--invalid-clickable': u.secure === false}" ng-mouseover="getKeyId(u.address)" wo-touch="invite(u)" wo-tooltip="#fingerprint-info">
|
<span class="label" ng-class="{'label--invalid': u.secure === false, 'label--invalid-clickable': u.secure === false}" ng-mouseover="getKeyId(u.address)" wo-touch="invite(u)" wo-tooltip="#fingerprint-info">
|
||||||
{{u.name || u.address}}
|
{{u.name || u.address}} <span ng-show="u.name" ng-class="{'read__sender-address': !notStripped}">< {{u.address}} ></span>
|
||||||
<svg ng-show="u.secure === false"><use xlink:href="#icon-add_contact" /></svg>
|
<svg ng-show="u.secure === false"><use xlink:href="#icon-add_contact" /></svg>
|
||||||
</span>
|
</span>
|
||||||
|
|
||||||
|
@ -72,7 +72,7 @@
|
|||||||
</ul>
|
</ul>
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
<textarea class="write__body" ng-model="body" spellcheck="true" wo-focus-me="state.lightbox === 'write' && writerTitle === 'Reply'" tabindex="3"></textarea>
|
<textarea class="write__body" ng-model="body" spellcheck="true" wo-focus-me="state.lightbox === 'write' && writerTitle === 'Reply'" tabindex="3" placeholder="Compose email"></textarea>
|
||||||
</div><!--/write-->
|
</div><!--/write-->
|
||||||
|
|
||||||
<footer class="lightbox__controls">
|
<footer class="lightbox__controls">
|
||||||
|
@ -100,6 +100,10 @@ describe('Email DAO integration tests', function() {
|
|||||||
}, {
|
}, {
|
||||||
raw: 'Message-id: <foobar>\r\nSubject: moveme\r\n\r\nmoveme!',
|
raw: 'Message-id: <foobar>\r\nSubject: moveme\r\n\r\nmoveme!',
|
||||||
uid: 900
|
uid: 900
|
||||||
|
}, {
|
||||||
|
description: "Thunderbird (no attachment - PGP/INLINE): Signed w/ unsigned content spoofing attack",
|
||||||
|
raw: "Message-ID: <53A87B12.9010706@gmail.com>\r\nDate: Mon, 23 Jun 2014 21:08:02 +0200\r\nFrom: Andris Testbox2 <safewithme.testuser@gmail.com>\r\nUser-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.9; rv:24.0) Gecko/20100101 Thunderbird/24.2.0\r\nMIME-Version: 1.0\r\nTo: safewithme.testuser@gmail.com\r\nSubject: test6\r\nX-Enigmail-Version: 1.6\r\nContent-Type: text/plain; charset=ISO-8859-1\r\nContent-Transfer-Encoding: 7bit\r\n\r\n\r\n-----BEGIN PGP SIGNED MESSAGE-----\r\nHash: SHA512\r\n\r\ntest6\r\n-----BEGIN PGP SIGNATURE-----\r\nVersion: GnuPG v1.4.13 (Darwin)\r\nComment: GPGTools - https://gpgtools.org\r\nComment: Using GnuPG with Thunderbird - http://www.enigmail.net/\r\n\r\niQEcBAEBCgAGBQJTqHsSAAoJENf7k/zfv8I8wz4H/RWo1qJvvJtMl7GyqGGbaByX\r\n/D7/yWJzMdE0Y7J/tHIexQ/sZnmcDlHG0mtJKgI7EOh2EyV+r+78vF71Mlc+bg8g\r\n3B4TKyp0QU1Pb6SETG//FtKrU7SnkjKujHvRMpzcOcm0ZLBDpmftyWLvp9Dg3KOF\r\n5sMBGpJRn1pqX2DxXZtc1rYOmSAaxFI5jewPws0DCDkLDGp9gLyusNeDHkmAT4AG\r\nDqsDPQvW0R4Sy7aQFT7GjrdnCiLyikynkocUpR95fDnjHJ6Xbyj2Yj9/ofewPQ//\r\nMq39sIYbcqlDBAhsOlii3ekdzLS4xEOkvtFoD4pufyLj3pYY60FG4bPygcccYkI=\r\n=IkRV\r\n-----END PGP SIGNATURE-----\r\n\r\nTHIS IS UNSINGED CONTENT AND MUST NOT BE SHOWN\r\n\r\n-----BEGIN PGP SIGNATURE-----\r\n-----END PGP SIGNATURE-----\r\n",
|
||||||
|
uid: 910
|
||||||
}];
|
}];
|
||||||
|
|
||||||
imapFolders = {
|
imapFolders = {
|
||||||
@ -245,6 +249,7 @@ describe('Email DAO integration tests', function() {
|
|||||||
function onCleaned() {
|
function onCleaned() {
|
||||||
userStorage = accountService._accountStore;
|
userStorage = accountService._accountStore;
|
||||||
auth = accountService._auth;
|
auth = accountService._auth;
|
||||||
|
emailDao = accountService._emailDao;
|
||||||
|
|
||||||
auth.setCredentials({
|
auth.setCredentials({
|
||||||
emailAddress: testAccount.user,
|
emailAddress: testAccount.user,
|
||||||
@ -253,19 +258,17 @@ describe('Email DAO integration tests', function() {
|
|||||||
imap: {} // a preconfigured smtpclient with mocked tcp sockets
|
imap: {} // a preconfigured smtpclient with mocked tcp sockets
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// stub rest request to key server
|
||||||
|
sinon.stub(emailDao._keychain._publicKeyDao, 'get').returns(resolves(mockKeyPair.publicKey));
|
||||||
|
sinon.stub(emailDao._keychain._publicKeyDao, 'getByUserId').returns(resolves(mockKeyPair.publicKey));
|
||||||
|
|
||||||
auth.init().then(function() {
|
auth.init().then(function() {
|
||||||
accountService.init({
|
accountService.init({
|
||||||
emailAddress: testAccount.user
|
emailAddress: testAccount.user
|
||||||
}).then(function() {
|
}).then(function() {
|
||||||
emailDao = accountService._emailDao;
|
|
||||||
|
|
||||||
// retrieve the pgpbuilder from the emaildao and initialize the pgpmailer with the existing pgpbuilder
|
// retrieve the pgpbuilder from the emaildao and initialize the pgpmailer with the existing pgpbuilder
|
||||||
pgpMailer = new PgpMailer({}, emailDao._pgpbuilder);
|
pgpMailer = new PgpMailer({}, emailDao._pgpbuilder);
|
||||||
|
|
||||||
// stub rest request to key server
|
|
||||||
sinon.stub(emailDao._keychain._publicKeyDao, 'get').returns(resolves(mockKeyPair.publicKey));
|
|
||||||
sinon.stub(emailDao._keychain._publicKeyDao, 'getByUserId').returns(resolves(mockKeyPair.publicKey));
|
|
||||||
|
|
||||||
emailDao.unlock({
|
emailDao.unlock({
|
||||||
passphrase: testAccount.pass,
|
passphrase: testAccount.pass,
|
||||||
keypair: mockKeyPair
|
keypair: mockKeyPair
|
||||||
@ -563,6 +566,14 @@ describe('Email DAO integration tests', function() {
|
|||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should parse Thunderbird (no attachment): Signed w/ PGP/INLINE including unsigned content spoofing attack', function() {
|
||||||
|
expect(inbox.messages[19].encrypted).to.be.false;
|
||||||
|
expect(inbox.messages[19].signed).to.be.true;
|
||||||
|
expect(inbox.messages[19].signaturesValid).to.be.true;
|
||||||
|
expect(inbox.messages[19].attachments.length).to.equal(0);
|
||||||
|
expect(inbox.messages[19].body).to.equal('test6');
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -24,9 +24,20 @@ describe('Action Bar Controller unit test', function() {
|
|||||||
type: 'Inbox',
|
type: 'Inbox',
|
||||||
path: 'INBOX',
|
path: 'INBOX',
|
||||||
messages: [{
|
messages: [{
|
||||||
|
from: [],
|
||||||
checked: true
|
checked: true
|
||||||
}, {
|
}, {
|
||||||
|
from: [],
|
||||||
checked: false
|
checked: false
|
||||||
|
}, {
|
||||||
|
from: [],
|
||||||
|
flagged: true
|
||||||
|
}, {
|
||||||
|
from: [],
|
||||||
|
encrypted: true
|
||||||
|
}, {
|
||||||
|
from: [],
|
||||||
|
unread: true
|
||||||
}]
|
}]
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@ -43,6 +54,83 @@ describe('Action Bar Controller unit test', function() {
|
|||||||
|
|
||||||
afterEach(function() {});
|
afterEach(function() {});
|
||||||
|
|
||||||
|
describe('check', function() {
|
||||||
|
it('should check all', function() {
|
||||||
|
scope.check(scope.CHECKALL);
|
||||||
|
|
||||||
|
expect(scope.state.nav.currentFolder.messages[0].checked).to.be.true;
|
||||||
|
expect(scope.state.nav.currentFolder.messages[1].checked).to.be.true;
|
||||||
|
expect(scope.state.nav.currentFolder.messages[2].checked).to.be.true;
|
||||||
|
expect(scope.state.nav.currentFolder.messages[3].checked).to.be.true;
|
||||||
|
expect(scope.state.nav.currentFolder.messages[4].checked).to.be.true;
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should check none', function() {
|
||||||
|
scope.check(scope.CHECKNONE);
|
||||||
|
expect(scope.state.nav.currentFolder.messages[0].checked).to.be.false;
|
||||||
|
expect(scope.state.nav.currentFolder.messages[1].checked).to.be.false;
|
||||||
|
expect(scope.state.nav.currentFolder.messages[2].checked).to.be.false;
|
||||||
|
expect(scope.state.nav.currentFolder.messages[3].checked).to.be.false;
|
||||||
|
expect(scope.state.nav.currentFolder.messages[4].checked).to.be.false;
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should check encrypted', function() {
|
||||||
|
scope.check(scope.CHECKENCRYPTED);
|
||||||
|
expect(scope.state.nav.currentFolder.messages[0].checked).to.be.false;
|
||||||
|
expect(scope.state.nav.currentFolder.messages[1].checked).to.be.false;
|
||||||
|
expect(scope.state.nav.currentFolder.messages[2].checked).to.be.false;
|
||||||
|
expect(scope.state.nav.currentFolder.messages[3].checked).to.be.true;
|
||||||
|
expect(scope.state.nav.currentFolder.messages[4].checked).to.be.false;
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should check unencrypted', function() {
|
||||||
|
scope.check(scope.CHECKUNENCRYPTED);
|
||||||
|
expect(scope.state.nav.currentFolder.messages[0].checked).to.be.true;
|
||||||
|
expect(scope.state.nav.currentFolder.messages[1].checked).to.be.true;
|
||||||
|
expect(scope.state.nav.currentFolder.messages[2].checked).to.be.true;
|
||||||
|
expect(scope.state.nav.currentFolder.messages[3].checked).to.be.false;
|
||||||
|
expect(scope.state.nav.currentFolder.messages[4].checked).to.be.true;
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should check unread', function() {
|
||||||
|
scope.check(scope.CHECKUNREAD);
|
||||||
|
expect(scope.state.nav.currentFolder.messages[0].checked).to.be.false;
|
||||||
|
expect(scope.state.nav.currentFolder.messages[1].checked).to.be.false;
|
||||||
|
expect(scope.state.nav.currentFolder.messages[2].checked).to.be.false;
|
||||||
|
expect(scope.state.nav.currentFolder.messages[3].checked).to.be.false;
|
||||||
|
expect(scope.state.nav.currentFolder.messages[4].checked).to.be.true;
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should check read', function() {
|
||||||
|
scope.check(scope.CHECKREAD);
|
||||||
|
expect(scope.state.nav.currentFolder.messages[0].checked).to.be.true;
|
||||||
|
expect(scope.state.nav.currentFolder.messages[1].checked).to.be.true;
|
||||||
|
expect(scope.state.nav.currentFolder.messages[2].checked).to.be.true;
|
||||||
|
expect(scope.state.nav.currentFolder.messages[3].checked).to.be.true;
|
||||||
|
expect(scope.state.nav.currentFolder.messages[4].checked).to.be.false;
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should check starred', function() {
|
||||||
|
scope.check(scope.CHECKFLAGGED);
|
||||||
|
expect(scope.state.nav.currentFolder.messages[0].checked).to.be.false;
|
||||||
|
expect(scope.state.nav.currentFolder.messages[1].checked).to.be.false;
|
||||||
|
expect(scope.state.nav.currentFolder.messages[2].checked).to.be.true;
|
||||||
|
expect(scope.state.nav.currentFolder.messages[3].checked).to.be.false;
|
||||||
|
expect(scope.state.nav.currentFolder.messages[4].checked).to.be.false;
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should check unstarred', function() {
|
||||||
|
scope.check(scope.CHECKUNFLAGGED);
|
||||||
|
expect(scope.state.nav.currentFolder.messages[0].checked).to.be.true;
|
||||||
|
expect(scope.state.nav.currentFolder.messages[1].checked).to.be.true;
|
||||||
|
expect(scope.state.nav.currentFolder.messages[2].checked).to.be.false;
|
||||||
|
expect(scope.state.nav.currentFolder.messages[3].checked).to.be.true;
|
||||||
|
expect(scope.state.nav.currentFolder.messages[4].checked).to.be.true;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
describe('deleteMessage', function() {
|
describe('deleteMessage', function() {
|
||||||
it('should not delete without a selected mail', function() {
|
it('should not delete without a selected mail', function() {
|
||||||
scope.deleteMessage();
|
scope.deleteMessage();
|
||||||
|
@ -1,12 +1,14 @@
|
|||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
var Auth = require('../../../../src/js/service/auth'),
|
var Auth = require('../../../../src/js/service/auth'),
|
||||||
|
Account = require('../../../../src/js/email/account'),
|
||||||
|
Dialog = require('../../../../src/js/util/dialog'),
|
||||||
LoginExistingCtrl = require('../../../../src/js/controller/login/login-existing'),
|
LoginExistingCtrl = require('../../../../src/js/controller/login/login-existing'),
|
||||||
EmailDAO = require('../../../../src/js/email/email'),
|
EmailDAO = require('../../../../src/js/email/email'),
|
||||||
KeychainDAO = require('../../../../src/js/service/keychain');
|
KeychainDAO = require('../../../../src/js/service/keychain');
|
||||||
|
|
||||||
describe('Login (existing user) Controller unit test', function() {
|
describe('Login (existing user) Controller unit test', function() {
|
||||||
var scope, location, ctrl, emailDaoMock, authMock,
|
var scope, location, ctrl, emailDaoMock, authMock, accountMock, dialogMock,
|
||||||
emailAddress = 'fred@foo.com',
|
emailAddress = 'fred@foo.com',
|
||||||
passphrase = 'asd',
|
passphrase = 'asd',
|
||||||
keychainMock;
|
keychainMock;
|
||||||
@ -15,6 +17,8 @@ describe('Login (existing user) Controller unit test', function() {
|
|||||||
emailDaoMock = sinon.createStubInstance(EmailDAO);
|
emailDaoMock = sinon.createStubInstance(EmailDAO);
|
||||||
authMock = sinon.createStubInstance(Auth);
|
authMock = sinon.createStubInstance(Auth);
|
||||||
keychainMock = sinon.createStubInstance(KeychainDAO);
|
keychainMock = sinon.createStubInstance(KeychainDAO);
|
||||||
|
accountMock = sinon.createStubInstance(Account);
|
||||||
|
dialogMock = sinon.createStubInstance(Dialog);
|
||||||
|
|
||||||
authMock.emailAddress = emailAddress;
|
authMock.emailAddress = emailAddress;
|
||||||
|
|
||||||
@ -31,7 +35,10 @@ describe('Login (existing user) Controller unit test', function() {
|
|||||||
$q: window.qMock,
|
$q: window.qMock,
|
||||||
email: emailDaoMock,
|
email: emailDaoMock,
|
||||||
auth: authMock,
|
auth: authMock,
|
||||||
keychain: keychainMock
|
keychain: keychainMock,
|
||||||
|
account: accountMock,
|
||||||
|
dialog: dialogMock,
|
||||||
|
appConfig: {}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -68,6 +68,14 @@ describe('Private Key DAO unit tests', function() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
describe('upload', function() {
|
describe('upload', function() {
|
||||||
|
beforeEach(function() {
|
||||||
|
sinon.stub(privkeyDao, '_getFolder');
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(function() {
|
||||||
|
privkeyDao._getFolder.restore();
|
||||||
|
});
|
||||||
|
|
||||||
it('should fail due to invalid args', function(done) {
|
it('should fail due to invalid args', function(done) {
|
||||||
privkeyDao.upload({}).catch(function(err) {
|
privkeyDao.upload({}).catch(function(err) {
|
||||||
expect(err.message).to.match(/Incomplete/);
|
expect(err.message).to.match(/Incomplete/);
|
||||||
@ -75,10 +83,11 @@ describe('Private Key DAO unit tests', function() {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should work', function(done) {
|
it('should work without existing folder', function(done) {
|
||||||
var IMAP_KEYS_FOLDER = 'openpgp_keys';
|
var IMAP_KEYS_FOLDER = 'openpgp_keys';
|
||||||
var fullPath = 'INBOX.' + IMAP_KEYS_FOLDER;
|
var fullPath = 'INBOX.' + IMAP_KEYS_FOLDER;
|
||||||
|
|
||||||
|
privkeyDao._getFolder.returns(rejects(new Error()));
|
||||||
imapClientStub.createFolder.withArgs({
|
imapClientStub.createFolder.withArgs({
|
||||||
path: IMAP_KEYS_FOLDER
|
path: IMAP_KEYS_FOLDER
|
||||||
}).returns(resolves(fullPath));
|
}).returns(resolves(fullPath));
|
||||||
@ -95,44 +104,96 @@ describe('Private Key DAO unit tests', function() {
|
|||||||
salt: salt,
|
salt: salt,
|
||||||
iv: iv
|
iv: iv
|
||||||
}).then(function() {
|
}).then(function() {
|
||||||
|
expect(privkeyDao._getFolder.calledOnce).to.be.true;
|
||||||
expect(imapClientStub.createFolder.calledOnce).to.be.true;
|
expect(imapClientStub.createFolder.calledOnce).to.be.true;
|
||||||
expect(imapClientStub.uploadMessage.calledOnce).to.be.true;
|
expect(imapClientStub.uploadMessage.calledOnce).to.be.true;
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should work with existing folder', function(done) {
|
||||||
|
var IMAP_KEYS_FOLDER = 'openpgp_keys';
|
||||||
|
var fullPath = 'INBOX.' + IMAP_KEYS_FOLDER;
|
||||||
|
|
||||||
|
privkeyDao._getFolder.returns(resolves(fullPath));
|
||||||
|
imapClientStub.uploadMessage.withArgs(sinon.match(function(arg) {
|
||||||
|
expect(arg.path).to.equal(fullPath);
|
||||||
|
expect(arg.message).to.exist;
|
||||||
|
return true;
|
||||||
|
})).returns(resolves());
|
||||||
|
|
||||||
|
privkeyDao.upload({
|
||||||
|
_id: keyId,
|
||||||
|
userId: emailAddress,
|
||||||
|
encryptedPrivateKey: encryptedPrivateKey,
|
||||||
|
salt: salt,
|
||||||
|
iv: iv
|
||||||
|
}).then(function() {
|
||||||
|
expect(privkeyDao._getFolder.calledOnce).to.be.true;
|
||||||
|
expect(imapClientStub.createFolder.called).to.be.false;
|
||||||
|
expect(imapClientStub.uploadMessage.calledOnce).to.be.true;
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('isSynced', function() {
|
describe('isSynced', function() {
|
||||||
beforeEach(function() {
|
beforeEach(function() {
|
||||||
|
sinon.stub(privkeyDao, '_getFolder');
|
||||||
sinon.stub(privkeyDao, '_fetchMessage');
|
sinon.stub(privkeyDao, '_fetchMessage');
|
||||||
});
|
});
|
||||||
afterEach(function() {
|
afterEach(function() {
|
||||||
|
privkeyDao._getFolder.restore();
|
||||||
privkeyDao._fetchMessage.restore();
|
privkeyDao._fetchMessage.restore();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should be synced', function(done) {
|
it('should be synced', function(done) {
|
||||||
|
|
||||||
|
privkeyDao._getFolder.returns(resolves('foo'));
|
||||||
privkeyDao._fetchMessage.returns(resolves({}));
|
privkeyDao._fetchMessage.returns(resolves({}));
|
||||||
|
|
||||||
privkeyDao.isSynced().then(function(synced) {
|
privkeyDao.isSynced().then(function(synced) {
|
||||||
expect(synced).to.be.true;
|
expect(synced).to.be.true;
|
||||||
|
expect(privkeyDao._getFolder.calledOnce).to.be.true;
|
||||||
|
expect(privkeyDao._fetchMessage.calledOnce).to.be.true;
|
||||||
|
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should not be synced', function(done) {
|
it('should not be synced', function(done) {
|
||||||
|
privkeyDao._getFolder.returns(resolves());
|
||||||
privkeyDao._fetchMessage.returns(resolves());
|
privkeyDao._fetchMessage.returns(resolves());
|
||||||
|
|
||||||
privkeyDao.isSynced().then(function(synced) {
|
privkeyDao.isSynced().then(function(synced) {
|
||||||
expect(synced).to.be.false;
|
expect(synced).to.be.false;
|
||||||
|
expect(privkeyDao._getFolder.calledOnce).to.be.true;
|
||||||
|
expect(privkeyDao._fetchMessage.calledOnce).to.be.true;
|
||||||
|
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should not be synced in case of error', function(done) {
|
it('should not be synced in case of error', function(done) {
|
||||||
|
privkeyDao._getFolder.returns(rejects(new Error()));
|
||||||
|
|
||||||
|
privkeyDao.isSynced().then(function(synced) {
|
||||||
|
expect(synced).to.be.false;
|
||||||
|
expect(privkeyDao._getFolder.calledOnce).to.be.true;
|
||||||
|
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should not be synced in case of error', function(done) {
|
||||||
|
privkeyDao._getFolder.returns(resolves('foo'));
|
||||||
privkeyDao._fetchMessage.returns(rejects(new Error()));
|
privkeyDao._fetchMessage.returns(rejects(new Error()));
|
||||||
|
|
||||||
privkeyDao.isSynced().then(function(synced) {
|
privkeyDao.isSynced().then(function(synced) {
|
||||||
expect(synced).to.be.false;
|
expect(synced).to.be.false;
|
||||||
|
expect(privkeyDao._getFolder.calledOnce).to.be.true;
|
||||||
|
expect(privkeyDao._fetchMessage.calledOnce).to.be.true;
|
||||||
|
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@ -146,15 +207,18 @@ describe('Private Key DAO unit tests', function() {
|
|||||||
}];
|
}];
|
||||||
|
|
||||||
beforeEach(function() {
|
beforeEach(function() {
|
||||||
|
sinon.stub(privkeyDao, '_getFolder');
|
||||||
sinon.stub(privkeyDao, '_fetchMessage');
|
sinon.stub(privkeyDao, '_fetchMessage');
|
||||||
sinon.stub(privkeyDao, '_parse');
|
sinon.stub(privkeyDao, '_parse');
|
||||||
});
|
});
|
||||||
afterEach(function() {
|
afterEach(function() {
|
||||||
|
privkeyDao._getFolder.restore();
|
||||||
privkeyDao._fetchMessage.restore();
|
privkeyDao._fetchMessage.restore();
|
||||||
privkeyDao._parse.restore();
|
privkeyDao._parse.restore();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should fail if key not synced', function(done) {
|
it('should fail if key not synced', function(done) {
|
||||||
|
privkeyDao._getFolder.returns(resolves('foo'));
|
||||||
privkeyDao._fetchMessage.returns(resolves());
|
privkeyDao._fetchMessage.returns(resolves());
|
||||||
|
|
||||||
privkeyDao.download({
|
privkeyDao.download({
|
||||||
@ -167,6 +231,7 @@ describe('Private Key DAO unit tests', function() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('should work', function(done) {
|
it('should work', function(done) {
|
||||||
|
privkeyDao._getFolder.returns(resolves('foo'));
|
||||||
privkeyDao._fetchMessage.returns(resolves({}));
|
privkeyDao._fetchMessage.returns(resolves({}));
|
||||||
imapClientStub.getBodyParts.returns(resolves());
|
imapClientStub.getBodyParts.returns(resolves());
|
||||||
privkeyDao._parse.returns(resolves(root));
|
privkeyDao._parse.returns(resolves(root));
|
||||||
@ -253,14 +318,7 @@ describe('Private Key DAO unit tests', function() {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('_fetchMessage', function() {
|
describe('_getFolder', function() {
|
||||||
it('should fail due to invalid args', function(done) {
|
|
||||||
privkeyDao._fetchMessage({}).catch(function(err) {
|
|
||||||
expect(err.message).to.match(/Incomplete/);
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should fail if imap folder does not exist', function(done) {
|
it('should fail if imap folder does not exist', function(done) {
|
||||||
imapClientStub.listWellKnownFolders.returns(resolves({
|
imapClientStub.listWellKnownFolders.returns(resolves({
|
||||||
Inbox: [{
|
Inbox: [{
|
||||||
@ -270,13 +328,10 @@ describe('Private Key DAO unit tests', function() {
|
|||||||
path: 'foo'
|
path: 'foo'
|
||||||
}]
|
}]
|
||||||
}));
|
}));
|
||||||
imapClientStub.listMessages.returns(rejects(new Error()));
|
|
||||||
|
|
||||||
privkeyDao._fetchMessage({
|
privkeyDao._getFolder().catch(function(err) {
|
||||||
userId: emailAddress,
|
|
||||||
keyId: keyId
|
|
||||||
}).catch(function(err) {
|
|
||||||
expect(err.message).to.match(/Imap folder/);
|
expect(err.message).to.match(/Imap folder/);
|
||||||
|
expect(imapClientStub.listWellKnownFolders.calledOnce).to.be.true;
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@ -290,6 +345,25 @@ describe('Private Key DAO unit tests', function() {
|
|||||||
path: 'openpgp_keys'
|
path: 'openpgp_keys'
|
||||||
}]
|
}]
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
privkeyDao._getFolder().then(function(path) {
|
||||||
|
expect(path).to.equal('openpgp_keys');
|
||||||
|
expect(imapClientStub.listWellKnownFolders.calledOnce).to.be.true;
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('_fetchMessage', function() {
|
||||||
|
it('should fail due to invalid args', function(done) {
|
||||||
|
privkeyDao._fetchMessage({}).catch(function(err) {
|
||||||
|
expect(err.message).to.match(/Incomplete/);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
it('should work', function(done) {
|
||||||
imapClientStub.listMessages.returns(resolves([{
|
imapClientStub.listMessages.returns(resolves([{
|
||||||
subject: keyId
|
subject: keyId
|
||||||
}]));
|
}]));
|
||||||
@ -299,21 +373,12 @@ describe('Private Key DAO unit tests', function() {
|
|||||||
keyId: keyId
|
keyId: keyId
|
||||||
}).then(function(msg) {
|
}).then(function(msg) {
|
||||||
expect(msg.subject).to.equal(keyId);
|
expect(msg.subject).to.equal(keyId);
|
||||||
expect(imapClientStub.listWellKnownFolders.calledOnce).to.be.true;
|
|
||||||
expect(imapClientStub.listMessages.calledOnce).to.be.true;
|
expect(imapClientStub.listMessages.calledOnce).to.be.true;
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should work with path prefix', function(done) {
|
it('should work with path prefix', function(done) {
|
||||||
imapClientStub.listWellKnownFolders.returns(resolves({
|
|
||||||
Inbox: [{
|
|
||||||
path: 'INBOX'
|
|
||||||
}],
|
|
||||||
Other: [{
|
|
||||||
path: 'INBOX.openpgp_keys'
|
|
||||||
}]
|
|
||||||
}));
|
|
||||||
imapClientStub.listMessages.returns(resolves([{
|
imapClientStub.listMessages.returns(resolves([{
|
||||||
subject: keyId
|
subject: keyId
|
||||||
}]));
|
}]));
|
||||||
@ -323,18 +388,12 @@ describe('Private Key DAO unit tests', function() {
|
|||||||
keyId: keyId
|
keyId: keyId
|
||||||
}).then(function(msg) {
|
}).then(function(msg) {
|
||||||
expect(msg.subject).to.equal(keyId);
|
expect(msg.subject).to.equal(keyId);
|
||||||
expect(imapClientStub.listWellKnownFolders.calledOnce).to.be.true;
|
|
||||||
expect(imapClientStub.listMessages.calledOnce).to.be.true;
|
expect(imapClientStub.listMessages.calledOnce).to.be.true;
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should work for not matching message', function(done) {
|
it('should work for not matching message', function(done) {
|
||||||
imapClientStub.listWellKnownFolders.returns(resolves({
|
|
||||||
Other: [{
|
|
||||||
path: 'INBOX.openpgp_keys'
|
|
||||||
}]
|
|
||||||
}));
|
|
||||||
imapClientStub.listMessages.returns(resolves([{
|
imapClientStub.listMessages.returns(resolves([{
|
||||||
subject: '7890'
|
subject: '7890'
|
||||||
}]));
|
}]));
|
||||||
@ -349,11 +408,6 @@ describe('Private Key DAO unit tests', function() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('should work for no messages', function(done) {
|
it('should work for no messages', function(done) {
|
||||||
imapClientStub.listWellKnownFolders.returns(resolves({
|
|
||||||
Other: [{
|
|
||||||
path: 'INBOX.openpgp_keys'
|
|
||||||
}]
|
|
||||||
}));
|
|
||||||
imapClientStub.listMessages.returns(resolves([]));
|
imapClientStub.listMessages.returns(resolves([]));
|
||||||
|
|
||||||
privkeyDao._fetchMessage({
|
privkeyDao._fetchMessage({
|
||||||
|
Loading…
Reference in New Issue
Block a user