1
0
mirror of https://github.com/moparisthebest/mail synced 2024-11-25 18:32:20 -05:00

Merge pull request #203 from whiteout-io/dev/WO-722

Dev/wo 722
This commit is contained in:
Felix Hammerl 2014-11-27 16:26:31 +01:00
commit c0ee4a7db7
112 changed files with 2843 additions and 2731 deletions

View File

@ -155,46 +155,49 @@ module.exports = function(grunt) {
unitTest: {
files: {
'test/unit/index.browserified.js': [
'test/unit/oauth-test.js',
'test/unit/auth-test.js',
'test/unit/email-dao-test.js',
'test/unit/app-controller-test.js',
'test/unit/pgp-test.js',
'test/unit/crypto-test.js',
'test/unit/backbutton-handler-test.js',
'test/unit/rest-dao-test.js',
'test/unit/admin-dao-test.js',
'test/unit/publickey-dao-test.js',
'test/unit/privatekey-dao-test.js',
'test/unit/lawnchair-dao-test.js',
'test/unit/keychain-dao-test.js',
'test/unit/devicestorage-dao-test.js',
'test/unit/newsletter-service-test.js',
'test/unit/mail-config-service-test.js',
'test/unit/dialog-ctrl-test.js',
'test/unit/add-account-ctrl-test.js',
'test/unit/create-account-ctrl-test.js',
'test/unit/validate-phone-ctrl-test.js',
'test/unit/account-ctrl-test.js',
'test/unit/set-passphrase-ctrl-test.js',
'test/unit/contacts-ctrl-test.js',
'test/unit/login-existing-ctrl-test.js',
'test/unit/login-initial-ctrl-test.js',
'test/unit/login-new-device-ctrl-test.js',
'test/unit/login-privatekey-download-ctrl-test.js',
'test/unit/login-set-credentials-ctrl-test.js',
'test/unit/privatekey-upload-ctrl-test.js',
'test/unit/login-ctrl-test.js',
'test/unit/read-ctrl-test.js',
'test/unit/navigation-ctrl-test.js',
'test/unit/mail-list-ctrl-test.js',
'test/unit/write-ctrl-test.js',
'test/unit/action-bar-ctrl-test.js',
'test/unit/outbox-bo-test.js',
'test/unit/invitation-dao-test.js',
'test/unit/update-handler-test.js',
'test/unit/connection-doctor-test.js',
'test/main.js'
'test/main.js',
'test/unit/util/dialog-test.js',
'test/unit/util/connection-doctor-test.js',
'test/unit/util/update-handler-test.js',
'test/unit/util/backbutton-handler-test.js',
'test/unit/util/status-display-test.js',
'test/unit/crypto/pgp-test.js',
'test/unit/crypto/crypto-test.js',
'test/unit/service/rest-dao-test.js',
'test/unit/service/admin-dao-test.js',
'test/unit/service/auth-test.js',
'test/unit/service/oauth-test.js',
'test/unit/service/publickey-dao-test.js',
'test/unit/service/privatekey-dao-test.js',
'test/unit/service/lawnchair-dao-test.js',
'test/unit/service/keychain-dao-test.js',
'test/unit/service/devicestorage-dao-test.js',
'test/unit/service/newsletter-service-test.js',
'test/unit/service/mail-config-service-test.js',
'test/unit/service/invitation-dao-test.js',
'test/unit/email/outbox-bo-test.js',
'test/unit/email/email-dao-test.js',
'test/unit/email/account-test.js',
'test/unit/email/search-test.js',
'test/unit/controller/login/add-account-ctrl-test.js',
'test/unit/controller/login/create-account-ctrl-test.js',
'test/unit/controller/login/validate-phone-ctrl-test.js',
'test/unit/controller/login/login-existing-ctrl-test.js',
'test/unit/controller/login/login-initial-ctrl-test.js',
'test/unit/controller/login/login-new-device-ctrl-test.js',
'test/unit/controller/login/login-privatekey-download-ctrl-test.js',
'test/unit/controller/login/login-set-credentials-ctrl-test.js',
'test/unit/controller/login/login-ctrl-test.js',
'test/unit/controller/app/dialog-ctrl-test.js',
'test/unit/controller/app/privatekey-upload-ctrl-test.js',
'test/unit/controller/app/account-ctrl-test.js',
'test/unit/controller/app/set-passphrase-ctrl-test.js',
'test/unit/controller/app/contacts-ctrl-test.js',
'test/unit/controller/app/read-ctrl-test.js',
'test/unit/controller/app/navigation-ctrl-test.js',
'test/unit/controller/app/mail-list-ctrl-test.js',
'test/unit/controller/app/write-ctrl-test.js',
'test/unit/controller/app/action-bar-ctrl-test.js',
]
},
options: browserifyOpt
@ -202,8 +205,8 @@ module.exports = function(grunt) {
integrationTest: {
files: {
'test/integration/index.browserified.js': [
'test/integration/email-dao-test.js',
'test/main.js'
'test/main.js',
'test/integration/email-dao-test.js'
]
},
options: browserifyOpt
@ -256,7 +259,7 @@ module.exports = function(grunt) {
readSandbox: {
src: [
'node_modules/dompurify/purify.js',
'src/js/controller/read-sandbox.js'
'src/js/controller/app/read-sandbox.js'
],
dest: 'dist/js/read-sandbox.min.js'
},
@ -278,7 +281,6 @@ module.exports = function(grunt) {
'node_modules/jquery/dist/jquery.min.js',
'src/lib/angular/angular.js',
'src/lib/angular/angular-route.js',
'src/lib/angular/angular-animate.js',
'src/lib/angular/angular-mocks.js',
'src/lib/lawnchair/lawnchair-git.js',
'src/lib/lawnchair/lawnchair-adapter-webkit-sqlite-git.js',
@ -293,6 +295,9 @@ module.exports = function(grunt) {
integrationTest: {
src: [
'src/lib/underscore/underscore.js',
'node_modules/jquery/dist/jquery.min.js',
'src/lib/angular/angular.js',
'src/lib/angular/angular-mocks.js',
'src/lib/lawnchair/lawnchair-git.js',
'src/lib/lawnchair/lawnchair-adapter-webkit-sqlite-git.js',
'src/lib/lawnchair/lawnchair-adapter-indexed-db-git.js',
@ -446,11 +451,11 @@ module.exports = function(grunt) {
tasks: ['dist-js-app']
},
jsUnitTest: {
files: ['test/unit/*-test.js'],
files: ['test/unit/**/*-test.js', 'test/*.js'],
tasks: ['dist-js-unitTest']
},
jsIntegrationTest: {
files: ['test/integration/*-test.js'],
files: ['test/integration/*-test.js', 'test/*.js'],
tasks: ['dist-js-integrationTest']
},
icons: {

View File

@ -1,5 +1,5 @@
<!DOCTYPE html>
<html ng-app="mail" ng-csp manifest="appcache.manifest">
<html ng-csp manifest="appcache.manifest">
<head>
<meta charset="utf-8">
<title>Whiteout Mail</title>

View File

@ -1,9 +1,17 @@
'use strict';
var appCfg = {};
var ngModule = angular.module('woAppConfig', []);
ngModule.factory('appConfig', function() {
return appCfg;
});
module.exports = appCfg;
/**
* Global app configurations
*/
exports.config = {
appCfg.config = {
cloudUrl: 'https://keys.whiteout.io',
privkeyServerUrl: 'https://keychain.whiteout.io',
adminUrl: 'https://admin-node.whiteout.io',
@ -38,7 +46,7 @@ if (typeof chrome !== 'undefined' && chrome.runtime && chrome.runtime.getManifes
}
function setConfigParams(manifest) {
var cfg = exports.config;
var cfg = appCfg.config;
function getUrl(beginsWith) {
return _.find(manifest.permissions, function(permission) {
@ -57,7 +65,7 @@ function setConfigParams(manifest) {
/**
* Strings are maintained here
*/
exports.string = {
appCfg.string = {
fallbackSubject: '(no subject)',
invitationSubject: 'Invitation to a private conversation',
invitationMessage: 'Hi,\n\nI use Whiteout Mail to send and receive encrypted email. I would like to exchange encrypted messages with you as well.\n\nPlease install the Whiteout Mail application. This application makes it easy to read and write messages securely with PGP encryption applied.\n\nGo to the Whiteout Networks homepage to learn more and to download the application: https://whiteout.io\n\n',

View File

@ -1,307 +0,0 @@
/**
* The main application controller
*/
'use strict';
var axe = require('axe-logger'),
Auth = require('./bo/auth'),
PGP = require('./crypto/pgp'),
OAuth = require('./util/oauth'),
PgpMailer = require('pgpmailer'),
util = require('crypto-lib').util,
PgpBuilder = require('pgpbuilder'),
OutboxBO = require('./bo/outbox'),
mailreader = require('mailreader'),
ImapClient = require('imap-client'),
Crypto = require('./crypto/crypto'),
RestDAO = require('./dao/rest-dao'),
appConfig = require('./app-config'),
EmailDAO = require('./dao/email-dao'),
AdminDao = require('./dao/admin-dao'),
KeychainDAO = require('./dao/keychain-dao'),
PublicKeyDAO = require('./dao/publickey-dao'),
LawnchairDAO = require('./dao/lawnchair-dao'),
PrivateKeyDAO = require('./dao/privatekey-dao'),
InvitationDAO = require('./dao/invitation-dao'),
DeviceStorageDAO = require('./dao/devicestorage-dao'),
ConnectionDoctor = require('./util/connection-doctor'),
UpdateHandler = require('./util/update/update-handler'),
config = appConfig.config,
str = appConfig.string;
var ctrl = {};
/**
* Start the application.
*/
ctrl.start = function(options, callback) {
if (ctrl.started) {
return callback();
}
ctrl.started = true;
ctrl.onError = options.onError;
// are we running in a cordova app or in a browser environment?
if (window.cordova) {
// wait for 'deviceready' event to make sure plugins are loaded
axe.debug('Assuming Cordova environment...');
document.addEventListener("deviceready", onDeviceReady, false);
} else {
// No need to wait on events... just start the app
axe.debug('Assuming Browser environment...');
onDeviceReady();
}
function onDeviceReady() {
axe.debug('Starting app.');
ctrl.buildModules();
// Handle offline and online gracefully
window.addEventListener('online', ctrl.onConnect.bind(ctrl, ctrl.onError));
window.addEventListener('offline', ctrl.onDisconnect.bind(ctrl));
ctrl._appConfigStore.init('app-config', callback);
}
};
/**
* Initialize the dependency tree.
*/
ctrl.buildModules = function() {
var lawnchairDao, restDao, pubkeyDao, privkeyDao, crypto, emailDao, keychain, pgp, userStorage, pgpbuilder, oauth, appConfigStore, auth;
// start the mailreader's worker thread
mailreader.startWorker(config.workerPath + '/mailreader-parser-worker.min.js');
// init objects and inject dependencies
restDao = new RestDAO();
lawnchairDao = new LawnchairDAO();
pubkeyDao = new PublicKeyDAO(restDao);
privkeyDao = new PrivateKeyDAO(new RestDAO(config.privkeyServerUrl));
oauth = new OAuth(new RestDAO('https://www.googleapis.com'));
crypto = new Crypto();
ctrl._pgp = pgp = new PGP();
ctrl._keychain = keychain = new KeychainDAO(lawnchairDao, pubkeyDao, privkeyDao, crypto, pgp);
keychain.requestPermissionForKeyUpdate = function(params, callback) {
var message = params.newKey ? str.updatePublicKeyMsgNewKey : str.updatePublicKeyMsgRemovedKey;
message = message.replace('{0}', params.userId);
ctrl.onError({
title: str.updatePublicKeyTitle,
message: message,
positiveBtnStr: str.updatePublicKeyPosBtn,
negativeBtnStr: str.updatePublicKeyNegBtn,
showNegativeBtn: true,
callback: callback
});
};
ctrl._appConfigStore = appConfigStore = new DeviceStorageDAO(new LawnchairDAO());
ctrl._auth = auth = new Auth(appConfigStore, oauth, pgp);
ctrl._userStorage = userStorage = new DeviceStorageDAO(lawnchairDao);
ctrl._invitationDao = new InvitationDAO(restDao);
ctrl._pgpbuilder = pgpbuilder = new PgpBuilder();
ctrl._emailDao = emailDao = new EmailDAO(keychain, pgp, userStorage, pgpbuilder, mailreader);
ctrl._outboxBo = new OutboxBO(emailDao, keychain, userStorage);
ctrl._updateHandler = new UpdateHandler(appConfigStore, userStorage, auth);
ctrl._adminDao = new AdminDao(new RestDAO(config.adminUrl));
ctrl._doctor = new ConnectionDoctor();
emailDao.onError = ctrl.onError;
};
/**
* Calls runtime hooks to check if an app update is available.
*/
ctrl.checkForUpdate = function() {
ctrl._updateHandler.checkForUpdate(ctrl.onError);
};
/**
* Fire up the database, retrieve the available keys for the user and initialize the email data access object
*/
ctrl.init = function(options, callback) {
// account information for the email dao
var account = {
realname: options.realname,
emailAddress: options.emailAddress,
asymKeySize: config.asymKeySize
};
// Pre-Flight check: don't even start to initialize stuff if the email address is not valid
if (!util.validateEmailAddress(options.emailAddress)) {
return callback(new Error('The user email address is invalid!'));
}
prepareDatabase();
// Pre-Flight check: initialize and prepare user's local database
function prepareDatabase() {
ctrl._userStorage.init(options.emailAddress, function(err) {
if (err) {
return callback(err);
}
// Migrate the databases if necessary
ctrl._updateHandler.update(function(err) {
if (err) {
return callback(new Error('Updating the internal database failed. Please reinstall the app! Reason: ' + err.message));
}
prepareKeys();
});
});
}
// retrieve keypair fom devicestorage/cloud, refresh public key if signup was incomplete before
function prepareKeys() {
ctrl._keychain.getUserKeyPair(options.emailAddress, function(err, keys) {
if (err) {
return callback(err);
}
// this is either a first start on a new device, OR a subsequent start without completing the signup,
// since we can't differenciate those cases here, do a public key refresh because it might be outdated
if (keys && keys.publicKey && !keys.privateKey) {
ctrl._keychain.refreshKeyForUserId({
userId: options.emailAddress,
overridePermission: true
}, function(err, publicKey) {
if (err) {
return callback(err);
}
initEmailDao({
publicKey: publicKey
});
});
return;
}
// either signup was complete or no pubkey is available, so we're good here.
initEmailDao(keys);
});
}
function initEmailDao(keys) {
ctrl._emailDao.init({
account: account
}, function(err) {
if (err) {
return callback(err);
}
callback(null, keys);
});
}
};
/**
* Check if the user agent is online.
*/
ctrl.isOnline = function() {
return navigator.onLine;
};
/**
* Event handler that is called when the user agent goes offline.
*/
ctrl.onDisconnect = function() {
ctrl._emailDao.onDisconnect();
};
/**
* Log the current user out by clear the app config store and deleting instances of imap-client and pgp-mailer.
*/
ctrl.logout = function() {
// clear app config store
ctrl._auth.logout(function(err) {
if (err) {
ctrl.onError(err);
return;
}
// delete instance of imap-client and pgp-mailer
ctrl._emailDao.onDisconnect(function(err) {
if (err) {
ctrl.onError(err);
return;
}
if (typeof window.chrome !== 'undefined' && chrome.runtime && chrome.runtime.reload) {
// reload chrome app
chrome.runtime.reload();
} else {
// navigate to login
window.location.href = '/';
}
});
});
};
/**
* Event that is called when the user agent goes online. This create new instances of the imap-client and pgp-mailer and connects to the mail server.
*/
ctrl.onConnect = function(callback) {
if (!ctrl.isOnline() || !ctrl._emailDao || !ctrl._emailDao._account) {
// prevent connection infinite loop
callback();
return;
}
ctrl._auth.getCredentials(function(err, credentials) {
if (err) {
callback(err);
return;
}
initClients(credentials);
});
function initClients(credentials) {
// add the maximum update batch size for imap folders to the imap configuration
credentials.imap.maxUpdateSize = config.imapUpdateBatchSize;
// tls socket worker path for multithreaded tls in non-native tls environments
credentials.imap.tlsWorkerPath = credentials.smtp.tlsWorkerPath = config.workerPath + '/tcp-socket-tls-worker.min.js';
var pgpMailer = new PgpMailer(credentials.smtp, ctrl._pgpbuilder);
var imapClient = new ImapClient(credentials.imap);
imapClient.onError = onConnectionError;
pgpMailer.onError = onConnectionError;
// certificate update handling
imapClient.onCert = ctrl._auth.handleCertificateUpdate.bind(ctrl._auth, 'imap', ctrl.onConnect, ctrl.onError);
pgpMailer.onCert = ctrl._auth.handleCertificateUpdate.bind(ctrl._auth, 'smtp', ctrl.onConnect, ctrl.onError);
// connect to clients
ctrl._emailDao.onConnect({
imapClient: imapClient,
pgpMailer: pgpMailer,
ignoreUploadOnSent: ctrl._emailDao.checkIgnoreUploadOnSent(credentials.imap.host)
}, callback);
}
function onConnectionError(error) {
axe.debug('Connection error. Attempting reconnect in ' + config.reconnectInterval + ' ms. Error: ' + (error.errMsg || error.message) + (error.stack ? ('\n' + error.stack) : ''));
setTimeout(function() {
axe.debug('Reconnecting...');
// re-init client modules on error
ctrl.onConnect(function(err) {
if (err) {
axe.error('Reconnect attempt failed! ' + (err.errMsg || err.message) + (err.stack ? ('\n' + err.stack) : ''));
return;
}
axe.debug('Reconnect attempt complete.');
});
}, config.reconnectInterval);
}
};
module.exports = ctrl;

View File

@ -14,36 +14,49 @@ if (typeof window.applicationCache !== 'undefined') {
};
}
var DialogCtrl = require('./controller/dialog'),
AddAccountCtrl = require('./controller/add-account'),
CreateAccountCtrl = require('./controller/create-account'),
ValidatePhoneCtrl = require('./controller/validate-phone'),
AccountCtrl = require('./controller/account'),
SetPassphraseCtrl = require('./controller/set-passphrase'),
PrivateKeyUploadCtrl = require('./controller/privatekey-upload'),
ContactsCtrl = require('./controller/contacts'),
AboutCtrl = require('./controller/about'),
LoginCtrl = require('./controller/login'),
LoginInitialCtrl = require('./controller/login-initial'),
LoginNewDeviceCtrl = require('./controller/login-new-device'),
LoginExistingCtrl = require('./controller/login-existing'),
LoginPrivateKeyDownloadCtrl = require('./controller/login-privatekey-download'),
LoginSetCredentialsCtrl = require('./controller/login-set-credentials'),
MailListCtrl = require('./controller/mail-list'),
ReadCtrl = require('./controller/read'),
WriteCtrl = require('./controller/write'),
NavigationCtrl = require('./controller/navigation'),
ActionBarCtrl = require('./controller/action-bar'),
errorUtil = require('./util/error'),
var axe = require('axe-logger'),
AddAccountCtrl = require('./controller/login/add-account'),
CreateAccountCtrl = require('./controller/login/create-account'),
ValidatePhoneCtrl = require('./controller/login/validate-phone'),
LoginCtrl = require('./controller/login/login'),
LoginInitialCtrl = require('./controller/login/login-initial'),
LoginNewDeviceCtrl = require('./controller/login/login-new-device'),
LoginExistingCtrl = require('./controller/login/login-existing'),
LoginPrivateKeyDownloadCtrl = require('./controller/login/login-privatekey-download'),
LoginSetCredentialsCtrl = require('./controller/login/login-set-credentials'),
DialogCtrl = require('./controller/app/dialog'),
AccountCtrl = require('./controller/app/account'),
SetPassphraseCtrl = require('./controller/app/set-passphrase'),
PrivateKeyUploadCtrl = require('./controller/app/privatekey-upload'),
ContactsCtrl = require('./controller/app/contacts'),
AboutCtrl = require('./controller/app/about'),
MailListCtrl = require('./controller/app/mail-list'),
ReadCtrl = require('./controller/app/read'),
WriteCtrl = require('./controller/app/write'),
NavigationCtrl = require('./controller/app/navigation'),
ActionBarCtrl = require('./controller/app/action-bar'),
StatusDisplayCtrl = require('./controller/app/status-display'),
backButtonUtil = require('./util/backbutton-handler');
require('./directive/common'),
require('./service/newsletter'),
require('./service/mail-config');
// include angular modules
require('./app-config');
require('./directive/common');
require('./util');
require('./crypto');
require('./service');
require('./email');
// init main angular module including dependencies
var app = angular.module('mail', [
'ngRoute',
'ngAnimate',
'ngTagsInput',
'woAppConfig',
'woDirectives',
'woUtil',
'woCrypto',
'woServices',
'woEmail',
'navigation',
'mail-list',
'write',
@ -51,10 +64,7 @@ var app = angular.module('mail', [
'contacts',
'login-new-device',
'privatekey-upload',
'infinite-scroll',
'ngTagsInput',
'woDirectives',
'woServices'
'infinite-scroll'
]);
// set router paths
@ -111,9 +121,6 @@ app.run(function($rootScope) {
// global state... inherited to all child scopes
$rootScope.state = {};
// attach global error handler
errorUtil.attachHandler($rootScope);
// attach the back button handler to the root scope
backButtonUtil.attachHandler($rootScope);
@ -131,4 +138,26 @@ app.controller('PrivateKeyUploadCtrl', PrivateKeyUploadCtrl);
app.controller('ContactsCtrl', ContactsCtrl);
app.controller('AboutCtrl', AboutCtrl);
app.controller('DialogCtrl', DialogCtrl);
app.controller('ActionBarCtrl', ActionBarCtrl);
app.controller('ActionBarCtrl', ActionBarCtrl);
app.controller('StatusDisplayCtrl', StatusDisplayCtrl);
//
// Manual angular bootstraping
//
// are we running in a cordova app or in a browser environment?
if (window.cordova) {
// wait for 'deviceready' event to make sure plugins are loaded
axe.debug('Assuming Cordova environment...');
document.addEventListener('deviceready', bootstrap, false);
} else {
// No need to wait on events... just start the app
axe.debug('Assuming Browser environment...');
bootstrap();
}
function bootstrap() {
angular.element(document).ready(function() {
angular.bootstrap(document, ['mail']);
});
}

View File

@ -1,12 +1,6 @@
'use strict';
var cfg = require('../app-config').config;
//
// Controller
//
var AboutCtrl = function($scope) {
var AboutCtrl = function($scope, appConfig) {
$scope.state.about = {
toggle: function(to) {
@ -18,7 +12,7 @@ var AboutCtrl = function($scope) {
// scope variables
//
$scope.version = cfg.appVersion + ' (beta)';
$scope.version = appConfig.config.appVersion + ' (beta)';
$scope.date = new Date();
//

View File

@ -1,18 +1,10 @@
'use strict';
var appController = require('../app-controller'),
dl = require('../util/download'),
config = require('../app-config').config,
pgp, keychain, userId;
//
// Controller
//
var AccountCtrl = function($scope) {
userId = appController._emailDao._account.emailAddress;
keychain = appController._keychain;
pgp = appController._pgp;
var AccountCtrl = function($scope, auth, keychain, pgp, appConfig, download, dialog) {
var userId = auth.emailAddress;
if (!userId) {
return;
}
$scope.state.account = {
toggle: function(to) {
@ -31,7 +23,7 @@ var AccountCtrl = function($scope) {
var fpr = keyParams.fingerprint;
$scope.fingerprint = fpr.slice(0, 4) + ' ' + fpr.slice(4, 8) + ' ' + fpr.slice(8, 12) + ' ' + fpr.slice(12, 16) + ' ' + fpr.slice(16, 20) + ' ' + fpr.slice(20, 24) + ' ' + fpr.slice(24, 28) + ' ' + fpr.slice(28, 32) + ' ' + fpr.slice(32, 36) + ' ' + fpr.slice(36);
$scope.keysize = keyParams.bitSize;
$scope.publicKeyUrl = config.cloudUrl + '/' + userId;
$scope.publicKeyUrl = appConfig.config.cloudUrl + '/' + userId;
//
// scope functions
@ -40,14 +32,14 @@ var AccountCtrl = function($scope) {
$scope.exportKeyFile = function() {
keychain.getUserKeyPair(userId, function(err, keys) {
if (err) {
$scope.onError(err);
dialog.error(err);
return;
}
var keyId = keys.publicKey._id;
var file = 'whiteout_mail_' + userId + '_' + keyId.substring(8, keyId.length);
dl.createDownload({
download.createDownload({
content: keys.publicKey.publicKey + '\r\n' + keys.privateKey.encryptedKey,
filename: file + '.asc',
contentType: 'text/plain'

View File

@ -1,15 +1,6 @@
'use strict';
var appController = require('../app-controller'),
emailDao;
//
// Controller
//
var ActionBarCtrl = function($scope) {
emailDao = appController._emailDao;
var ActionBarCtrl = function($scope, email, dialog, statusDisplay) {
/**
* Move a single message from the currently selected folder to another folder
@ -24,9 +15,9 @@ var ActionBarCtrl = function($scope) {
// close read state
$scope.state.read.open = false;
$scope.state.mailList.updateStatus('Moving message...');
statusDisplay.update('Moving message...');
emailDao.moveMessage({
email.moveMessage({
folder: currentFolder(),
destination: destination,
message: message
@ -35,14 +26,14 @@ var ActionBarCtrl = function($scope) {
// show errors where appropriate
if (err.code === 42) {
$scope.select(message);
$scope.state.mailList.updateStatus('Unable to move message in offline mode!');
statusDisplay.update('Unable to move message in offline mode!');
return;
}
$scope.state.mailList.updateStatus('Error during move!');
$scope.onError(err);
statusDisplay.update('Error during move!');
dialog.error(err);
return;
}
$scope.state.mailList.updateStatus('Message moved.');
statusDisplay.update('Message moved.');
$scope.$apply();
});
};
@ -70,9 +61,9 @@ var ActionBarCtrl = function($scope) {
// close read state
$scope.state.read.open = false;
$scope.state.mailList.updateStatus('Deleting message...');
statusDisplay.update('Deleting message...');
emailDao.deleteMessage({
email.deleteMessage({
folder: currentFolder(),
message: message
}, function(err) {
@ -80,14 +71,14 @@ var ActionBarCtrl = function($scope) {
// show errors where appropriate
if (err.code === 42) {
$scope.select(message);
$scope.state.mailList.updateStatus('Unable to delete message in offline mode!');
statusDisplay.update('Unable to delete message in offline mode!');
return;
}
$scope.state.mailList.updateStatus('Error during delete!');
$scope.onError(err);
statusDisplay.update('Error during delete!');
dialog.error(err);
return;
}
$scope.state.mailList.updateStatus('Message deleted.');
statusDisplay.update('Message deleted.');
$scope.$apply();
});
};
@ -105,36 +96,38 @@ var ActionBarCtrl = function($scope) {
* @param {Object} message The message to be marked
* @param {boolean} unread If the message should be marked as read or unread
*/
$scope.markMessage = function(message, unread) {
$scope.markMessage = function(message, unread, keepOpen) {
if (!message) {
return;
}
$scope.state.mailList.updateStatus('Updating unread flag...');
statusDisplay.update('Updating unread flag...');
// close read state
$scope.state.read.open = false;
if (!keepOpen) {
$scope.state.read.open = false;
}
var originalState = message.unread;
message.unread = unread;
emailDao.setFlags({
email.setFlags({
folder: currentFolder(),
message: message
}, function(err) {
if (err && err.code === 42) {
// offline, restore
message.unread = originalState;
$scope.state.mailList.updateStatus('Unable to mark message in offline mode!');
statusDisplay.update('Unable to mark message in offline mode!');
return;
}
if (err) {
$scope.state.mailList.updateStatus('Error on sync!');
$scope.onError(err);
statusDisplay.update('Error on sync!');
dialog.error(err);
return;
}
$scope.state.mailList.updateStatus('Online');
statusDisplay.update('Online');
$scope.$apply();
});
};

View File

@ -1,15 +1,10 @@
'use strict';
var appController = require('../app-controller'),
keychain, pgp;
//
// Controller
//
var ContactsCtrl = function($scope) {
keychain = appController._keychain,
pgp = appController._pgp;
var ContactsCtrl = function($scope, keychain, pgp, dialog) {
$scope.state.contacts = {
toggle: function(to) {
@ -26,7 +21,7 @@ var ContactsCtrl = function($scope) {
$scope.listKeys = function() {
keychain.listLocalPublicKeys(function(err, keys) {
if (err) {
$scope.onError(err);
dialog.error(err);
return;
}
@ -54,7 +49,7 @@ var ContactsCtrl = function($scope) {
// verifiy public key string
if (publicKeyArmored.indexOf('-----BEGIN PGP PUBLIC KEY BLOCK-----') < 0) {
$scope.onError({
dialog.error({
showBugReporter: false,
message: 'Invalid public key!'
});
@ -64,7 +59,7 @@ var ContactsCtrl = function($scope) {
try {
keyParams = pgp.getKeyParams(publicKeyArmored);
} catch (e) {
$scope.onError(new Error('Error reading public key params!'));
dialog.error(new Error('Error reading public key params!'));
return;
}
@ -78,7 +73,7 @@ var ContactsCtrl = function($scope) {
keychain.saveLocalPublicKey(pubkey, function(err) {
if (err) {
$scope.onError(err);
dialog.error(err);
return;
}
@ -90,7 +85,7 @@ var ContactsCtrl = function($scope) {
$scope.removeKey = function(key) {
keychain.removeLocalPublicKey(key._id, function(err) {
if (err) {
$scope.onError(err);
dialog.error(err);
return;
}

View File

@ -0,0 +1,62 @@
'use strict';
var DialogCtrl = function($scope, $timeout, dialog) {
$scope.state.dialog = {
open: false
};
//
// Set dialog disply functions
//
dialog.displayInfo = function(options) {
return $timeout(function() {
setOptions(options);
});
};
dialog.displayError = function(options) {
return $timeout(function() {
if (!options) {
return;
}
setOptions(options);
$scope.title = options.title || 'Error';
$scope.showBugReporter = (typeof options.showBugReporter !== 'undefined' ? options.showBugReporter : !options.title); // if title is set, presume it's not an error by default
});
};
dialog.displayConfirm = function(options) {
return $timeout(function() {
setOptions(options);
});
};
function setOptions(options) {
$scope.state.dialog.open = true;
$scope.title = options.title;
$scope.message = options.errMsg || options.message;
$scope.faqLink = options.faqLink;
$scope.positiveBtnStr = options.positiveBtnStr || 'Ok';
$scope.negativeBtnStr = options.negativeBtnStr || 'Cancel';
$scope.showNegativeBtn = options.showNegativeBtn || false;
$scope.callback = options.callback;
}
//
// Scope functions
//
$scope.confirm = function(ok) {
$scope.state.dialog.open = false;
if ($scope.callback) {
$scope.callback(ok);
}
$scope.callback = undefined;
};
};
module.exports = DialogCtrl;

View File

@ -1,8 +1,6 @@
'use strict';
var appController = require('../app-controller'),
notification = require('../util/notification'),
emailDao, outboxBo, keychainDao, searchTimeout, firstSelect;
var searchTimeout, firstSelect;
//
// Constants
@ -13,14 +11,13 @@ var INIT_DISPLAY_LEN = 20,
FOLDER_TYPE_INBOX = 'Inbox',
NOTIFICATION_INBOX_TIMEOUT = 5000;
var MailListCtrl = function($scope, $routeParams) {
var MailListCtrl = function($scope, $timeout, $routeParams, statusDisplay, notification, email, keychain, dialog, search, dummy) {
//
// Init
//
emailDao = appController._emailDao;
outboxBo = appController._outboxBo;
keychainDao = appController._keychain;
$scope.state.mailList = {};
/**
* Gathers unread notifications to be cancelled later
@ -31,39 +28,39 @@ var MailListCtrl = function($scope, $routeParams) {
// scope functions
//
$scope.getBody = function(email) {
emailDao.getBody({
$scope.getBody = function(message) {
email.getBody({
folder: currentFolder(),
message: email
message: message
}, function(err) {
if (err && err.code !== 42) {
$scope.onError(err);
dialog.error(err);
return;
}
// display fetched body
$scope.$digest();
// automatically decrypt if it's the selected email
if (email === currentMessage()) {
emailDao.decryptBody({
message: email
}, $scope.onError);
// automatically decrypt if it's the selected message
if (message === currentMessage()) {
email.decryptBody({
message: message
}, dialog.error);
}
});
};
/**
* Called when clicking on an email list item
* Called when clicking on an message list item
*/
$scope.select = function(email) {
$scope.select = function(message) {
// unselect an item
if (!email) {
if (!message) {
$scope.state.mailList.selected = undefined;
return;
}
$scope.state.mailList.selected = email;
$scope.state.mailList.selected = message;
if (!firstSelect) {
// only toggle to read view on 2nd select in mobile mode
@ -71,22 +68,22 @@ var MailListCtrl = function($scope, $routeParams) {
}
firstSelect = false;
keychainDao.refreshKeyForUserId({
userId: email.from[0].address
keychain.refreshKeyForUserId({
userId: message.from[0].address
}, onKeyRefreshed);
function onKeyRefreshed(err) {
if (err) {
$scope.onError(err);
dialog.error(err);
}
emailDao.decryptBody({
message: email
}, $scope.onError);
email.decryptBody({
message: message
}, dialog.error);
// if the email is unread, please sync the new state.
// if the message is unread, please sync the new state.
// otherweise forget about it.
if (!email.unread) {
if (!message.unread) {
return;
}
@ -97,21 +94,16 @@ var MailListCtrl = function($scope, $routeParams) {
}
}
$scope.state.actionBar.markMessage(email, false);
$scope.state.actionBar.markMessage(message, false, true);
}
};
// share local scope functions with root state
$scope.state.mailList = {
updateStatus: updateStatus
};
//
// watch tasks
//
/**
* List emails from folder when user changes folder
* List messages from folder when user changes folder
*/
$scope._stopWatchTask = $scope.$watch('state.nav.currentFolder', function() {
if (!currentFolder()) {
@ -123,8 +115,8 @@ var MailListCtrl = function($scope, $routeParams) {
// in development, display dummy mail objects
if ($routeParams.dev) {
updateStatus('Last update: ', new Date());
currentFolder().messages = createDummyMails();
statusDisplay.update('Last update: ', new Date());
currentFolder().messages = dummy.listMails();
return;
}
@ -183,104 +175,37 @@ var MailListCtrl = function($scope, $routeParams) {
if (!searchText) {
// set display buffer to first messages
$scope.displayMessages = currentFolder().messages.slice(0, INIT_DISPLAY_LEN);
setSearching(false);
updateStatus('Online');
statusDisplay.setSearching(false);
statusDisplay.update('Online');
return;
}
// display searching spinner
setSearching(true);
updateStatus('Searching ...');
statusDisplay.setSearching(true);
statusDisplay.update('Searching ...');
searchTimeout = setTimeout(function() {
$scope.$apply(function() {
// filter relevant messages
$scope.displayMessages = $scope.search(currentFolder().messages, searchText);
setSearching(false);
updateStatus('Matches in this folder');
$scope.displayMessages = search.filter(currentFolder().messages, searchText);
statusDisplay.setSearching(false);
statusDisplay.update('Matches in this folder');
});
}, 500);
};
/**
* Do full text search on messages. Parse meta data first
*/
$scope.search = function(messages, searchText) {
// don't filter on empty searchText
if (!searchText) {
return messages;
}
// escape search string
searchText = searchText.replace(/([.*+?^${}()|\[\]\/\\])/g, "\\$1");
// compare all strings (case insensitive)
var regex = new RegExp(searchText, 'i');
function contains(input) {
if (!input) {
return false;
}
return regex.test(input);
}
function checkAddresses(header) {
if (!header || !header.length) {
return false;
}
for (var i = 0; i < header.length; i++) {
if (contains(header[i].name) || contains(header[i].address)) {
return true;
}
}
return false;
}
/**
* Filter meta data first and then only look at plaintext and decrypted message bodies
*/
function matchMetaDataFirst(m) {
// compare subject
if (contains(m.subject)) {
return true;
}
// compares address headers
if (checkAddresses(m.from) || checkAddresses(m.to) || checkAddresses(m.cc) || checkAddresses(m.bcc)) {
return true;
}
// compare plaintext body
if (m.body && !m.encrypted && contains(m.body)) {
return true;
}
// compare decrypted body
if (m.body && m.encrypted && m.decrypted && contains(m.body)) {
return true;
}
// compare plaintex html body
if (m.html && !m.encrypted && contains(m.html)) {
return true;
}
// compare decrypted html body
if (m.html && m.encrypted && m.decrypted && contains(m.html)) {
return true;
}
return false;
}
// user native js Array.filter
return messages.filter(matchMetaDataFirst);
};
/**
* Sync current folder when client comes back online
*/
$scope.watchOnline = $scope.$watch('account.online', function(isOnline) {
if (isOnline) {
updateStatus('Online');
openCurrentFolder();
} else {
updateStatus('Offline mode');
}
// wait one cycle for the status display controllers to init
$timeout(function() {
if (isOnline) {
statusDisplay.update('Online');
openCurrentFolder();
} else {
statusDisplay.update('Offline mode');
}
});
}, true);
//
@ -292,7 +217,7 @@ var MailListCtrl = function($scope, $routeParams) {
return;
}
emailDao.openFolder({
email.openFolder({
folder: currentFolder()
}, function(error) {
// dont wait until scroll to load visible mail bodies
@ -302,21 +227,12 @@ var MailListCtrl = function($scope, $routeParams) {
if (error && error.code === 42) {
return;
}
$scope.onError(error);
dialog.error(error);
});
}
function updateStatus(lbl, time) {
$scope.state.mailList.lastUpdateLbl = lbl;
$scope.state.mailList.lastUpdate = (time) ? time : '';
}
function setSearching(state) {
$scope.state.mailList.searching = state;
}
function currentFolder() {
return $scope.state.nav.currentFolder;
return $scope.state.nav && $scope.state.nav.currentFolder;
}
function currentMessage() {
@ -327,7 +243,7 @@ var MailListCtrl = function($scope, $routeParams) {
// Notification API
//
(emailDao || {}).onIncomingMessage = function(msgs) {
(email || {}).onIncomingMessage = function(msgs) {
var note, title, message, unreadMsgs;
unreadMsgs = msgs.filter(function(msg) {
@ -402,7 +318,7 @@ ngModule.directive('listScroll', function() {
}
for (var i = 0, len = listItems.length; i < len; i++) {
// the n-th list item (the dom representation of an email) corresponds to
// the n-th list item (the dom representation of an message) corresponds to
// the n-th message model in the filteredMessages array
listItem = listItems.item(i).getBoundingClientRect();
@ -458,72 +374,4 @@ function byUidDescending(a, b) {
}
}
// Helper for development mode
function createDummyMails() {
var uid = 1000000;
var Email = function(unread, attachments, answered) {
this.uid = uid--;
this.from = [{
name: 'Whiteout Support',
address: 'support@whiteout.io'
}]; // sender address
this.to = [{
address: 'max.musterman@gmail.com'
}, {
address: 'max.musterman@gmail.com'
}]; // list of receivers
this.cc = [{
address: 'john.doe@gmail.com'
}]; // list of receivers
this.attachments = attachments ? [{
"filename": "a.md",
"filesize": 123,
"mimeType": "text/x-markdown",
"part": "2",
"content": null
}, {
"filename": "b.md",
"filesize": 456,
"mimeType": "text/x-markdown",
"part": "3",
"content": null
}, {
"filename": "c.md",
"filesize": 789,
"mimeType": "text/x-markdown",
"part": "4",
"content": null
}] : [];
this.unread = unread;
this.answered = answered;
this.sentDate = new Date('Thu Sep 19 2013 20:41:23 GMT+0200 (CEST)');
this.subject = 'Getting started'; // Subject line
this.body = 'And a good day to you too sir. \n' +
'\n' +
'Thursday, Apr 24, 2014 3:33 PM safewithme.testuser@gmail.com wrote:\n' +
'> adsfadfasdfasdfasfdasdfasdfas\n' +
'\n' +
'http://example.com\n' +
'\n' +
'> Tuesday, Mar 25, 2014 4:19 PM gianniarcore@gmail.com wrote:\n' +
'>> from 0.7.0.1\n' +
'>>\n' +
'>> God speed!'; // plaintext body
//this.html = '<!DOCTYPE html><html><head></head><body><h1 style="border: 1px solid red; width: 500px;">Hello there' + Math.random() + '</h1></body></html>';
this.encrypted = true;
this.decrypted = true;
};
var dummies = [],
i = 100;
while (i--) {
// every second/third/fourth dummy mail with unread/attachments/answered
dummies.push(new Email((i % 2 === 0), (i % 3 === 0), (i % 5 === 0)));
}
return dummies;
}
module.exports = MailListCtrl;

View File

@ -1,12 +1,6 @@
'use strict';
var appController = require('../app-controller'),
notification = require('../util/notification'),
backBtnHandler = require('../util/backbutton-handler'),
appCfg = require('../app-config'),
config = appCfg.config,
str = appCfg.string,
emailDao, outboxBo;
var backBtnHandler = require('../../util/backbutton-handler');
//
// Constants
@ -19,14 +13,14 @@ var NOTIFICATION_SENT_TIMEOUT = 2000;
// Controller
//
var NavigationCtrl = function($scope, $routeParams, $location) {
if (!appController._emailDao && !$routeParams.dev) {
var NavigationCtrl = function($scope, $routeParams, $location, account, email, outbox, notification, appConfig, dialog) {
if (!$routeParams.dev && !account.isLoggedIn()) {
$location.path('/'); // init app
return;
}
emailDao = appController._emailDao;
outboxBo = appController._outboxBo;
var str = appConfig.string,
config = appConfig.config;
//
// scope functions
@ -46,32 +40,31 @@ var NavigationCtrl = function($scope, $routeParams, $location) {
$scope.onOutboxUpdate = function(err, count) {
if (err) {
$scope.onError(err);
dialog.error(err);
return;
}
// update the outbox mail count
var outbox = _.findWhere($scope.account.folders, {
var ob = _.findWhere($scope.account.folders, {
type: config.outboxMailboxType
});
outbox.count = count;
ob.count = count;
$scope.$apply();
emailDao.refreshFolder({
folder: outbox
}, $scope.onError);
email.refreshFolder({
folder: ob
}, dialog.error);
};
$scope.logout = function() {
$scope.onError({
dialog.confirm({
title: str.logoutTitle,
message: str.logoutMessage,
callback: function(confirm) {
if (confirm) {
appController.logout();
account.logout();
}
},
sync: true
}
});
};
@ -89,9 +82,9 @@ var NavigationCtrl = function($scope, $routeParams, $location) {
$scope.openFolder($scope.account.folders[0]);
}
// connect imap/smtp clients on first startup
appController.onConnect(function(err) {
account.onConnect(function(err) {
if (err) {
$scope.onError(err);
dialog.error(err);
return;
}
@ -114,18 +107,18 @@ var NavigationCtrl = function($scope, $routeParams, $location) {
}
// get pointer to account/folder/message tree on root scope
$scope.$root.account = emailDao._account;
$scope.$root.account = account.list()[0];
// set notificatio handler for sent messages
outboxBo.onSent = sentNotification;
outbox.onSent = sentNotification;
// start checking outbox periodically
outboxBo.startChecking($scope.onOutboxUpdate);
outbox.startChecking($scope.onOutboxUpdate);
}
function sentNotification(email) {
function sentNotification(message) {
notification.create({
title: 'Message sent',
message: email.subject,
message: message.subject,
timeout: NOTIFICATION_SENT_TIMEOUT
}, function() {});
}

View File

@ -1,12 +1,8 @@
'use strict';
var appController = require('../app-controller'),
util = require('crypto-lib').util,
keychain, pgp;
var util = require('crypto-lib').util;
var PrivateKeyUploadCtrl = function($scope) {
keychain = appController._keychain;
pgp = keychain._pgp;
var PrivateKeyUploadCtrl = function($scope, keychain, pgp, dialog, auth) {
$scope.state.privateKeyUpload = {
toggle: function(to) {
@ -24,7 +20,7 @@ var PrivateKeyUploadCtrl = function($scope) {
// close lightbox
$scope.state.lightbox = undefined;
// show message
$scope.onError({
dialog.info({
title: 'Info',
message: 'Your PGP key has already been synced.'
});
@ -64,7 +60,7 @@ var PrivateKeyUploadCtrl = function($scope) {
keyId: keyParams._id
}, function(err, privateKeySynced) {
if (err) {
$scope.onError(err);
dialog.error(err);
return;
}
@ -93,8 +89,7 @@ var PrivateKeyUploadCtrl = function($scope) {
if (inputCode.toUpperCase() !== $scope.code) {
var err = new Error('The code does not match. Please go back and check the generated code.');
err.sync = true;
$scope.onError(err);
dialog.error(err);
return false;
}
@ -106,7 +101,7 @@ var PrivateKeyUploadCtrl = function($scope) {
};
$scope.encryptAndUploadKey = function(callback) {
var userId = appController._emailDao._account.emailAddress;
var userId = auth.emailAddress;
var code = $scope.code;
// register device to keychain service
@ -114,7 +109,7 @@ var PrivateKeyUploadCtrl = function($scope) {
userId: userId
}, function(err) {
if (err) {
$scope.onError(err);
dialog.error(err);
return;
}
@ -147,7 +142,7 @@ var PrivateKeyUploadCtrl = function($scope) {
// set device name to local storage
$scope.setDeviceName(function(err) {
if (err) {
$scope.onError(err);
dialog.error(err);
return;
}
@ -158,14 +153,14 @@ var PrivateKeyUploadCtrl = function($scope) {
// init key sync
$scope.encryptAndUploadKey(function(err) {
if (err) {
$scope.onError(err);
dialog.error(err);
return;
}
// close sync dialog
$scope.state.privateKeyUpload.toggle(false);
// show success message
$scope.onError({
dialog.info({
title: 'Success',
message: 'Whiteout Keychain setup successful!'
});

View File

@ -1,21 +1,12 @@
'use strict';
var appController = require('../app-controller'),
download = require('../util/download'),
str = require('../app-config').string,
emailDao, invitationDao, outbox, pgp, keychain;
//
// Controller
//
var ReadCtrl = function($scope) {
var ReadCtrl = function($scope, email, invitation, outbox, pgp, keychain, appConfig, download, auth, dialog) {
emailDao = appController._emailDao;
invitationDao = appController._invitationDao;
outbox = appController._outboxBo;
pgp = appController._pgp;
keychain = appController._keychain;
var str = appConfig.string;
// set default value so that the popover height is correct on init
$scope.keyId = 'No key found.';
@ -31,7 +22,7 @@ var ReadCtrl = function($scope) {
$scope.keyId = 'Searching...';
keychain.getReceiverPublicKey(address, function(err, pubkey) {
if (err) {
$scope.onError(err);
dialog.error(err);
return;
}
@ -71,7 +62,7 @@ var ReadCtrl = function($scope) {
keychain.getReceiverPublicKey(user.address, function(err, pubkey) {
if (err) {
$scope.onError(err);
dialog.error(err);
return;
}
@ -97,12 +88,12 @@ var ReadCtrl = function($scope) {
}
var folder = $scope.state.nav.currentFolder;
var email = $scope.state.mailList.selected;
emailDao.getAttachment({
var message = $scope.state.mailList.selected;
email.getAttachment({
folder: folder,
uid: email.uid,
uid: message.uid,
attachment: attachment
}, $scope.onError);
}, dialog.error);
};
$scope.invite = function(user) {
@ -113,15 +104,15 @@ var ReadCtrl = function($scope) {
$scope.keyId = 'Sending invitation...';
var sender = emailDao._account.emailAddress,
var sender = auth.emailAddress,
recipient = user.address;
invitationDao.invite({
invitation.invite({
recipient: recipient,
sender: sender
}, function(err) {
if (err) {
$scope.onError(err);
dialog.error(err);
return;
}
@ -139,7 +130,7 @@ var ReadCtrl = function($scope) {
};
// send invitation mail
outbox.put(invitationMail, $scope.onError);
outbox.put(invitationMail, dialog.error);
});
};
};

View File

@ -1,15 +1,10 @@
'use strict';
var appController = require('../app-controller'),
pgp, keychain;
var SetPassphraseCtrl = function($scope, pgp, keychain, dialog) {
//
// Controller
//
var SetPassphraseCtrl = function($scope) {
keychain = appController._keychain;
pgp = appController._pgp;
//
// scope variables
//
$scope.state.setPassphrase = {
toggle: function(to) {
@ -22,10 +17,6 @@ var SetPassphraseCtrl = function($scope) {
}
};
//
// scope variables
//
//
// scope functions
//
@ -87,7 +78,7 @@ var SetPassphraseCtrl = function($scope) {
var keyId = pgp.getKeyParams()._id;
keychain.lookupPrivateKey(keyId, function(err, savedKey) {
if (err) {
$scope.onError(err);
dialog.error(err);
return;
}
@ -102,7 +93,7 @@ var SetPassphraseCtrl = function($scope) {
function onPassphraseChanged(err, newPrivateKeyArmored) {
if (err) {
err.showBugReporter = false;
$scope.onError(err);
dialog.error(err);
return;
}
@ -120,13 +111,13 @@ var SetPassphraseCtrl = function($scope) {
function onKeyPersisted(err) {
if (err) {
$scope.onError(err);
dialog.error(err);
return;
}
$scope.state.setPassphrase.toggle(false);
$scope.$apply();
$scope.onError({
dialog.info({
title: 'Success',
message: 'Passphrase change complete.'
});

View File

@ -0,0 +1,16 @@
'use strict';
var StatusDisplayCtrl = function($scope) {
$scope.$on('status', function(e, text, time) {
$scope.text = text;
$scope.time = (time) ? time : '';
});
$scope.$on('searching', function(e, state) {
$scope.searching = state;
});
};
module.exports = StatusDisplayCtrl;

View File

@ -1,21 +1,14 @@
'use strict';
var appController = require('../app-controller'),
axe = require('axe-logger'),
util = require('crypto-lib').util,
str = require('../app-config').string,
pgp, emailDao, outbox, keychainDao, auth;
var util = require('crypto-lib').util;
//
// Controller
//
var WriteCtrl = function($scope, $filter, $q) {
pgp = appController._pgp;
auth = appController._auth;
emailDao = appController._emailDao;
outbox = appController._outboxBo;
keychainDao = appController._keychain;
var WriteCtrl = function($scope, $filter, $q, appConfig, auth, keychain, pgp, email, outbox, dialog, axe) {
var str = appConfig.string;
// set default value so that the popover height is correct on init
$scope.keyId = 'XXXXXXXX';
@ -133,7 +126,7 @@ var WriteCtrl = function($scope, $filter, $q) {
}
if (replyAll) {
re.to.concat(re.cc).forEach(function(recipient) {
var me = emailDao._account.emailAddress;
var me = auth.emailAddress;
if (recipient.address === me && replyTo !== me) {
// don't reply to yourself
return;
@ -225,15 +218,15 @@ var WriteCtrl = function($scope, $filter, $q) {
return;
}
// keychainDao is undefined in local dev environment
if (keychainDao) {
// keychain is undefined in local dev environment
if (keychain) {
// check if to address is contained in known public keys
// when we write an email, we always need to work with the latest keys available
keychainDao.refreshKeyForUserId({
keychain.refreshKeyForUserId({
userId: recipient.address
}, function(err, key) {
if (err) {
$scope.onError(err);
dialog.error(err);
return;
}
@ -318,13 +311,13 @@ var WriteCtrl = function($scope, $filter, $q) {
//
$scope.sendToOutbox = function() {
var email;
var message;
// build email model for smtp-client
email = {
message = {
from: [{
name: emailDao._account.realname,
address: emailDao._account.emailAddress
name: auth.realname,
address: auth.emailAddress
}],
to: $scope.to.filter(filterEmptyAddresses),
cc: $scope.cc.filter(filterEmptyAddresses),
@ -337,11 +330,11 @@ var WriteCtrl = function($scope, $filter, $q) {
};
if ($scope.inReplyTo) {
email.headers['in-reply-to'] = '<' + $scope.inReplyTo + '>';
message.headers['in-reply-to'] = '<' + $scope.inReplyTo + '>';
}
if ($scope.references && $scope.references.length) {
email.headers.references = $scope.references.map(function(reference) {
message.headers.references = $scope.references.map(function(reference) {
return '<' + reference + '>';
}).join(' ');
}
@ -350,9 +343,9 @@ var WriteCtrl = function($scope, $filter, $q) {
$scope.state.writer.close();
// persist the email to disk for later sending
outbox.put(email, function(err) {
outbox.put(message, function(err) {
if (err) {
$scope.onError(err);
dialog.error(err);
return;
}
@ -363,12 +356,12 @@ var WriteCtrl = function($scope, $filter, $q) {
}
$scope.replyTo.answered = true;
emailDao.setFlags({
email.setFlags({
folder: currentFolder(),
message: $scope.replyTo
}, function(err) {
if (err && err.code !== 42) {
$scope.onError(err);
dialog.error(err);
return;
}
@ -396,9 +389,9 @@ var WriteCtrl = function($scope, $filter, $q) {
if (!$scope.addressBookCache) {
// populate address book cache
keychainDao.listLocalPublicKeys(function(err, keys) {
keychain.listLocalPublicKeys(function(err, keys) {
if (err) {
$scope.onError(err);
dialog.error(err);
return;
}

View File

@ -1,14 +0,0 @@
'use strict';
var DialogCtrl = function($scope) {
$scope.confirm = function(ok) {
$scope.state.dialog.open = false;
if ($scope.state.dialog.callback) {
$scope.state.dialog.callback(ok);
}
$scope.state.dialog.callback = undefined;
};
};
module.exports = DialogCtrl;

View File

@ -1,12 +1,7 @@
'use strict';
var appCtrl = require('../app-controller');
var AddAccountCtrl = function($scope, $location, $routeParams, mailConfig) {
if (!appCtrl._auth && !$routeParams.dev) {
$location.path('/'); // init app
return;
}
var AddAccountCtrl = function($scope, $location, $routeParams, mailConfig, auth, dialog) {
!$routeParams.dev && !auth.isInitialized() && $location.path('/'); // init app
$scope.getAccountSettings = function() {
if ($scope.form.$invalid) {
@ -25,7 +20,7 @@ var AddAccountCtrl = function($scope, $location, $routeParams, mailConfig) {
};
var hostname = config.imap.hostname;
if (appCtrl._auth.useOAuth(hostname)) {
if (auth.useOAuth(hostname)) {
// check for oauth support
$scope.oauthPossible();
} else {
@ -41,7 +36,7 @@ var AddAccountCtrl = function($scope, $location, $routeParams, mailConfig) {
$scope.oauthPossible = function() {
// ask user to use the platform's native OAuth api
$scope.onError({
dialog.confirm({
title: 'Google Account Login',
message: 'You are signing into a Google account. Would you like to sign in with Google or just continue with a password login?',
positiveBtnStr: 'Google sign in',
@ -62,9 +57,9 @@ var AddAccountCtrl = function($scope, $location, $routeParams, mailConfig) {
function getOAuthToken() {
// fetches the email address from the chrome identity api
appCtrl._auth.getOAuthToken(function(err) {
auth.getOAuthToken(function(err) {
if (err) {
return $scope.onError(err);
return dialog.error(err);
}
$scope.setCredentials();
$scope.$apply();

View File

@ -1,13 +1,7 @@
'use strict';
var appCtrl = require('../app-controller'),
cfg = require('../app-config').config;
var CreateAccountCtrl = function($scope, $location, $routeParams) {
if (!appCtrl._auth && !$routeParams.dev) {
$location.path('/'); // init app
return;
}
var CreateAccountCtrl = function($scope, $location, $routeParams, auth, admin, appConfig) {
!$routeParams.dev && !auth.isInitialized() && $location.path('/'); // init app
$scope.createWhiteoutAccount = function() {
if ($scope.form.$invalid) {
@ -17,17 +11,17 @@ var CreateAccountCtrl = function($scope, $location, $routeParams) {
$scope.busy = true;
$scope.errMsg = undefined; // reset error msg
var emailAddress = $scope.user + '@' + cfg.wmailDomain;
var emailAddress = $scope.user + '@' + appConfig.config.wmailDomain;
// set to state for next view
$scope.state.createAccount = {
auth.setCredentials({
emailAddress: emailAddress,
pass: $scope.pass,
password: $scope.pass,
realname: $scope.realname
};
});
// call REST api
appCtrl._adminDao.createUser({
admin.createUser({
emailAddress: emailAddress,
password: $scope.pass,
phone: $scope.phone.replace(/\s+/g, ''), // remove spaces from the phone number

View File

@ -1,14 +1,7 @@
'use strict';
var appController = require('../app-controller');
var LoginExistingCtrl = function($scope, $location, $routeParams) {
if (!appController._emailDao && !$routeParams.dev) {
$location.path('/'); // init app
return;
}
var emailDao = appController._emailDao;
var LoginExistingCtrl = function($scope, $location, $routeParams, email, auth, keychain) {
!$routeParams.dev && !auth.isInitialized() && $location.path('/'); // init app
$scope.confirmPassphrase = function() {
if ($scope.form.$invalid) {
@ -24,14 +17,14 @@ var LoginExistingCtrl = function($scope, $location, $routeParams) {
};
function unlockCrypto() {
var userId = emailDao._account.emailAddress;
emailDao._keychain.getUserKeyPair(userId, function(err, keypair) {
var userId = auth.emailAddress;
keychain.getUserKeyPair(userId, function(err, keypair) {
if (err) {
displayError(err);
return;
}
emailDao.unlock({
email.unlock({
keypair: keypair,
passphrase: $scope.passphrase
}, onUnlock);
@ -44,7 +37,7 @@ var LoginExistingCtrl = function($scope, $location, $routeParams) {
return;
}
appController._auth.storeCredentials(function(err) {
auth.storeCredentials(function(err) {
if (err) {
displayError(err);
return;

View File

@ -1,17 +1,9 @@
'use strict';
var appController = require('../app-controller');
var LoginInitialCtrl = function($scope, $location, $routeParams, newsletter, email, auth) {
!$routeParams.dev && !auth.isInitialized() && $location.path('/'); // init app
var LoginInitialCtrl = function($scope, $location, $routeParams, newsletter) {
if (!appController._emailDao && !$routeParams.dev) {
$location.path('/'); // init app
return;
}
if (appController._emailDao) {
var emailDao = appController._emailDao,
emailAddress = emailDao._account.emailAddress;
}
var emailAddress = auth.emailAddress;
var termsMsg = 'You must accept the Terms of Service to continue.',
states = {
@ -59,7 +51,7 @@ var LoginInitialCtrl = function($scope, $location, $routeParams, newsletter) {
// go to set keygen screen
$scope.setState(states.PROCESSING);
emailDao.unlock({
email.unlock({
passphrase: undefined // generate key without passphrase
}, function(err) {
if (err) {
@ -67,7 +59,7 @@ var LoginInitialCtrl = function($scope, $location, $routeParams, newsletter) {
return;
}
appController._auth.storeCredentials(function(err) {
auth.storeCredentials(function(err) {
if (err) {
displayError(err);
return;

View File

@ -1,15 +1,7 @@
'use strict';
var appController = require('../app-controller');
var LoginExistingCtrl = function($scope, $location, $routeParams) {
if (!appController._emailDao && !$routeParams.dev) {
$location.path('/'); // init app
return;
}
var emailDao = appController._emailDao,
pgp = appController._pgp;
var LoginExistingCtrl = function($scope, $location, $routeParams, email, auth, pgp, keychain) {
!$routeParams.dev && !auth.isInitialized() && $location.path('/'); // init app
$scope.incorrect = false;
@ -27,9 +19,9 @@ var LoginExistingCtrl = function($scope, $location, $routeParams) {
};
function unlockCrypto() {
var userId = emailDao._account.emailAddress;
var userId = auth.emailAddress;
// check if user already has a public key on the key server
emailDao._keychain.getUserKeyPair(userId, function(err, keypair) {
keychain.getUserKeyPair(userId, function(err, keypair) {
if (err) {
$scope.displayError(err);
return;
@ -76,7 +68,7 @@ var LoginExistingCtrl = function($scope, $location, $routeParams) {
}
// import and validate keypair
emailDao.unlock({
email.unlock({
keypair: keypair,
passphrase: $scope.passphrase
}, function(err) {
@ -86,7 +78,7 @@ var LoginExistingCtrl = function($scope, $location, $routeParams) {
return;
}
emailDao._keychain.putUserKeyPair(keypair, onUnlock);
keychain.putUserKeyPair(keypair, onUnlock);
});
});
}
@ -97,7 +89,7 @@ var LoginExistingCtrl = function($scope, $location, $routeParams) {
return;
}
appController._auth.storeCredentials(function(err) {
auth.storeCredentials(function(err) {
if (err) {
$scope.displayError(err);
return;

View File

@ -1,18 +1,7 @@
'use strict';
var appController = require('../app-controller');
var LoginPrivateKeyDownloadCtrl = function($scope, $location, $routeParams) {
if (!appController._emailDao && !$routeParams.dev) {
$location.path('/'); // init app
return;
}
if (appController._emailDao) {
var keychain = appController._keychain,
emailDao = appController._emailDao,
userId = emailDao._account.emailAddress;
}
var LoginPrivateKeyDownloadCtrl = function($scope, $location, $routeParams, auth, email, keychain) {
!$routeParams.dev && !auth.isInitialized() && $location.path('/'); // init app
$scope.step = 1;
@ -38,6 +27,7 @@ var LoginPrivateKeyDownloadCtrl = function($scope, $location, $routeParams) {
};
$scope.verifyRecoveryToken = function(callback) {
var userId = auth.emailAddress;
keychain.getUserKeyPair(userId, function(err, keypair) {
if (err) {
displayError(err);
@ -95,7 +85,7 @@ var LoginPrivateKeyDownloadCtrl = function($scope, $location, $routeParams) {
$scope.cachedKeypair.privateKey = privateKey;
// try empty passphrase
emailDao.unlock({
email.unlock({
keypair: $scope.cachedKeypair,
passphrase: undefined
}, function(err) {
@ -106,7 +96,7 @@ var LoginPrivateKeyDownloadCtrl = function($scope, $location, $routeParams) {
}
// passphrase is corrent ... go to main app
appController._auth.storeCredentials(function(err) {
auth.storeCredentials(function(err) {
if (err) {
displayError(err);
return;

View File

@ -4,16 +4,8 @@ var ENCRYPTION_METHOD_NONE = 0;
var ENCRYPTION_METHOD_STARTTLS = 1;
var ENCRYPTION_METHOD_TLS = 2;
var appCtrl = require('../app-controller');
var SetCredentialsCtrl = function($scope, $location, $routeParams) {
if (!appCtrl._auth && !$routeParams.dev) {
$location.path('/'); // init app
return;
}
var auth = appCtrl._auth;
var doctor = appCtrl._doctor;
var SetCredentialsCtrl = function($scope, $location, $routeParams, auth, connectionDoctor) {
!$routeParams.dev && !auth.isInitialized() && $location.path('/'); // init app
//
// Presets and Settings
@ -87,11 +79,11 @@ var SetCredentialsCtrl = function($scope, $location, $routeParams) {
};
// use the credentials in the connection doctor
doctor.configure(credentials);
connectionDoctor.configure(credentials);
// run connection doctor test suite
$scope.busy = true;
doctor.check(function(err) {
connectionDoctor.check(function(err) {
if (err) {
// display the error in the settings UI
$scope.connectionError = err;

View File

@ -1,45 +1,35 @@
'use strict';
var appController = require('../app-controller');
var LoginCtrl = function($scope, $timeout, $location, updateHandler, account, auth, email, keychain, dialog) {
var LoginCtrl = function($scope, $location) {
// start main application controller
appController.start({
onError: $scope.onError
}, function(err) {
if (err) {
$scope.onError(err);
return;
}
// check for app update
appController.checkForUpdate();
initializeUser();
});
// check for app update
updateHandler.checkForUpdate();
// initialize the user account
initializeUser();
function initializeUser() {
// init the auth modules
auth.init();
// get OAuth token from chrome
appController._auth.getEmailAddress(function(err, info) {
auth.getEmailAddress(function(err, info) {
if (err) {
$scope.onError(err);
dialog.error(err);
return;
}
// check if account needs to be selected
if (!info.emailAddress) {
goTo('/add-account');
$scope.goTo('/add-account');
return;
}
// initiate controller by creating email dao
appController.init({
// initiate the account by initializing the email dao and user storage
account.init({
emailAddress: info.emailAddress,
realname: info.realname
}, function(err, availableKeys) {
if (err) {
$scope.onError(err);
dialog.error(err);
return;
}
@ -51,54 +41,54 @@ var LoginCtrl = function($scope, $location) {
function redirect(availableKeys) {
if (availableKeys && availableKeys.publicKey && availableKeys.privateKey) {
// public and private key available, try empty passphrase
appController._emailDao.unlock({
email.unlock({
keypair: availableKeys,
passphrase: undefined
}, function(err) {
if (err) {
goTo('/login-existing');
$scope.goTo('/login-existing');
return;
}
appController._auth.storeCredentials(function(err) {
auth.storeCredentials(function(err) {
if (err) {
return $scope.onError(err);
return dialog.error(err);
}
goTo('/desktop');
$scope.goTo('/desktop');
});
});
} else if (availableKeys && availableKeys.publicKey && !availableKeys.privateKey) {
// check if private key is synced
appController._keychain.requestPrivateKeyDownload({
keychain.requestPrivateKeyDownload({
userId: availableKeys.publicKey.userId,
keyId: availableKeys.publicKey._id,
}, function(err, privateKeySynced) {
if (err) {
$scope.onError(err);
dialog.error(err);
return;
}
if (privateKeySynced) {
// private key is synced, proceed to download
goTo('/login-privatekey-download');
$scope.goTo('/login-privatekey-download');
return;
}
// no private key, import key file
goTo('/login-new-device');
$scope.goTo('/login-new-device');
});
} else {
// no public key available, start onboarding process
goTo('/login-initial');
$scope.goTo('/login-initial');
}
}
function goTo(location) {
$scope.$apply(function() {
$scope.goTo = function(location) {
return $timeout(function() {
$location.path(location);
});
}
};
};
module.exports = LoginCtrl;

View File

@ -1,12 +1,7 @@
'use strict';
var appCtrl = require('../app-controller');
var ValidatePhoneCtrl = function($scope, $location, $routeParams, mailConfig) {
if (!appCtrl._auth && !$routeParams.dev) {
$location.path('/'); // init app
return;
}
var ValidatePhoneCtrl = function($scope, $location, $routeParams, mailConfig, auth, admin) {
!$routeParams.dev && !auth.isInitialized() && $location.path('/'); // init app
$scope.validateUser = function() {
if ($scope.form.$invalid) {
@ -18,8 +13,8 @@ var ValidatePhoneCtrl = function($scope, $location, $routeParams, mailConfig) {
$scope.errMsg = undefined; // reset error msg
// verify user to REST api
appCtrl._adminDao.validateUser({
emailAddress: $scope.state.createAccount.emailAddress,
admin.validateUser({
emailAddress: auth.emailAddress,
token: $scope.token.toUpperCase()
}, function(err) {
if (err) {
@ -35,14 +30,14 @@ var ValidatePhoneCtrl = function($scope, $location, $routeParams, mailConfig) {
};
$scope.login = function() {
var address = $scope.state.createAccount.emailAddress;
var address = auth.emailAddress;
return mailConfig.get(address).then(function(config) {
// store credentials in memory
appCtrl._auth.setCredentials({
emailAddress: $scope.state.createAccount.emailAddress,
username: $scope.state.createAccount.emailAddress,
realname: $scope.state.createAccount.realname,
password: $scope.state.createAccount.pass,
auth.setCredentials({
emailAddress: auth.emailAddress,
username: auth.emailAddress,
realname: auth.realname,
password: auth.password,
imap: {
host: config.imap.hostname,
port: parseInt(config.imap.port, 10),
@ -65,4 +60,4 @@ var ValidatePhoneCtrl = function($scope, $location, $routeParams, mailConfig) {
};
};
module.exports = ValidatePhoneCtrl;
module.exports = ValidatePhoneCtrl;

View File

@ -1,16 +1,19 @@
/**
* High level crypto api that invokes native crypto (if available) and
* gracefully degrades to JS crypto (if unavailable)
*/
'use strict';
var ngModule = angular.module('woCrypto');
ngModule.service('crypto', Crypto);
module.exports = Crypto;
var aes = require('crypto-lib').aes,
pbkdf2 = require('./pbkdf2'),
config = require('../app-config').config,
axe = require('axe-logger');
var Crypto = function() {};
/**
* High level crypto api that invokes native crypto (if available) and
* gracefully degrades to JS crypto (if unavailable)
*/
function Crypto() {}
/**
* Encrypt plaintext using AES-GCM.
@ -115,6 +118,4 @@ function startWorker(options) {
return;
}
options.callback(null, result);
}
module.exports = Crypto;
}

6
src/js/crypto/index.js Normal file
View File

@ -0,0 +1,6 @@
'use strict';
angular.module('woCrypto', ['woAppConfig', 'woUtil']);
require('./pgp');
require('./crypto');

View File

@ -1,16 +1,19 @@
/**
* High level crypto api that handles all calls to OpenPGP.js
*/
'use strict';
var ngModule = angular.module('woCrypto');
ngModule.service('pgp', PGP);
module.exports = PGP;
var util = openpgp.util,
config = require('../app-config').config;
var PGP = function() {
/**
* High level crypto api that handles all calls to OpenPGP.js
*/
function PGP() {
openpgp.config.prefer_hash_algorithm = openpgp.enums.hash.sha256;
openpgp.initWorker(config.workerPath + '/openpgp.worker.min.js');
};
}
/**
* Generate a key pair for the user
@ -426,6 +429,4 @@ function checkSignatureValidity(signatures) {
// everything is in order
return true;
}
module.exports = PGP;
}

236
src/js/email/account.js Normal file
View File

@ -0,0 +1,236 @@
'use strict';
var ngModule = angular.module('woEmail');
ngModule.service('account', Account);
module.exports = Account;
var axe = require('axe-logger'),
util = require('crypto-lib').util,
PgpMailer = require('pgpmailer'),
ImapClient = require('imap-client');
function Account(appConfig, auth, accountStore, email, outbox, keychain, updateHandler, pgpbuilder, dialog) {
this._appConfig = appConfig;
this._auth = auth;
this._accountStore = accountStore;
this._emailDao = email;
this._outbox = outbox;
this._keychain = keychain;
this._updateHandler = updateHandler;
this._pgpbuilder = pgpbuilder;
this._dialog = dialog;
this._accounts = []; // init accounts list
}
/**
* Check if the account is already logged in.
* @return {Boolean} if the account is logged in
*/
Account.prototype.isLoggedIn = function() {
return (this._accounts.length > 0);
};
/**
* Lists all of the current accounts connected to the app
* @return {Array<Object>} The account objects containing folder and message objects
*/
Account.prototype.list = function() {
return this._accounts;
};
/**
* Fire up the database, retrieve the available keys for the user and initialize the email data access object
*/
Account.prototype.init = function(options, callback) {
var self = this;
// account information for the email dao
var account = {
realname: options.realname,
emailAddress: options.emailAddress,
asymKeySize: this._appConfig.asymKeySize
};
// Pre-Flight check: don't even start to initialize stuff if the email address is not valid
if (!util.validateEmailAddress(options.emailAddress)) {
return callback(new Error('The user email address is invalid!'));
}
prepareDatabase();
// Pre-Flight check: initialize and prepare user's local database
function prepareDatabase() {
try {
self._accountStore.init(options.emailAddress);
} catch (err) {
callback(err);
return;
}
// Migrate the databases if necessary
self._updateHandler.update(function(err) {
if (err) {
return callback(new Error('Updating the internal database failed. Please reinstall the app! Reason: ' + err.message));
}
prepareKeys();
});
}
// retrieve keypair fom devicestorage/cloud, refresh public key if signup was incomplete before
function prepareKeys() {
self._keychain.getUserKeyPair(options.emailAddress, function(err, keys) {
if (err) {
return callback(err);
}
// this is either a first start on a new device, OR a subsequent start without completing the signup,
// since we can't differenciate those cases here, do a public key refresh because it might be outdated
if (keys && keys.publicKey && !keys.privateKey) {
self._keychain.refreshKeyForUserId({
userId: options.emailAddress,
overridePermission: true
}, function(err, publicKey) {
if (err) {
return callback(err);
}
initEmailDao({
publicKey: publicKey
});
});
return;
}
// either signup was complete or no pubkey is available, so we're good here.
initEmailDao(keys);
});
}
function initEmailDao(keys) {
self._emailDao.init({
account: account
}, function(err) {
if (err) {
return callback(err);
}
// Handle offline and online gracefully ... arm dom event
window.addEventListener('online', self.onConnect.bind(self));
window.addEventListener('offline', self.onDisconnect.bind(self));
// add account object to the accounts array for the ng controllers
self._accounts.push(account);
callback(null, keys);
});
}
};
/**
* Check if the user agent is online.
*/
Account.prototype.isOnline = function() {
return navigator.onLine;
};
/**
* Event that is called when the user agent goes online. This create new instances of the imap-client and pgp-mailer and connects to the mail server.
*/
Account.prototype.onConnect = function() {
var self = this;
var config = self._appConfig.config;
if (!self.isOnline() || !self._emailDao || !self._emailDao._account) {
// prevent connection infinite loop
return;
}
self._auth.getCredentials(function(err, credentials) {
if (err) {
self._dialog.error(err);
return;
}
initClients(credentials);
});
function initClients(credentials) {
// add the maximum update batch size for imap folders to the imap configuration
credentials.imap.maxUpdateSize = config.imapUpdateBatchSize;
// tls socket worker path for multithreaded tls in non-native tls environments
credentials.imap.tlsWorkerPath = credentials.smtp.tlsWorkerPath = config.workerPath + '/tcp-socket-tls-worker.min.js';
var pgpMailer = new PgpMailer(credentials.smtp, self._pgpbuilder);
var imapClient = new ImapClient(credentials.imap);
imapClient.onError = onConnectionError;
pgpMailer.onError = onConnectionError;
// certificate update handling
imapClient.onCert = self._auth.handleCertificateUpdate.bind(self._auth, 'imap', self.onConnect.bind(self), self._dialog.error);
pgpMailer.onCert = self._auth.handleCertificateUpdate.bind(self._auth, 'smtp', self.onConnect.bind(self), self._dialog.error);
// connect to clients
self._emailDao.onConnect({
imapClient: imapClient,
pgpMailer: pgpMailer,
ignoreUploadOnSent: self._emailDao.checkIgnoreUploadOnSent(credentials.imap.host)
}, self._dialog.error);
}
function onConnectionError(error) {
axe.debug('Connection error. Attempting reconnect in ' + config.reconnectInterval + ' ms. Error: ' + (error.errMsg || error.message) + (error.stack ? ('\n' + error.stack) : ''));
setTimeout(function() {
axe.debug('Reconnecting...');
// re-init client modules on error
self.onConnect(function(err) {
if (err) {
axe.error('Reconnect attempt failed! ' + (err.errMsg || err.message) + (err.stack ? ('\n' + err.stack) : ''));
return;
}
axe.debug('Reconnect attempt complete.');
});
}, config.reconnectInterval);
}
};
/**
* Event handler that is called when the user agent goes offline.
*/
Account.prototype.onDisconnect = function() {
this._emailDao.onDisconnect();
};
/**
* Logout of an email account. Log the current user out by clear the app config store and deleting instances of imap-client and pgp-mailer.
*/
Account.prototype.logout = function() {
var self = this;
// clear app config store
self._auth.logout(function(err) {
if (err) {
self._dialog.error(err);
return;
}
// delete instance of imap-client and pgp-mailer
self._emailDao.onDisconnect(function(err) {
if (err) {
self._dialog.error(err);
return;
}
if (typeof window.chrome !== 'undefined' && chrome.runtime && chrome.runtime.reload) {
// reload chrome app
chrome.runtime.reload();
} else {
// navigate to login
window.location.href = '/';
}
});
});
};

View File

@ -1,5 +1,9 @@
'use strict';
var ngModule = angular.module('woEmail');
ngModule.service('email', Email);
module.exports = Email;
var config = require('../app-config').config,
str = require('../app-config').string;
@ -32,7 +36,7 @@ var MSG_PART_TYPE_HTML = 'html';
//
//
// Email Dao
// Email Service
//
//
@ -46,13 +50,14 @@ var MSG_PART_TYPE_HTML = 'html';
* @param {Object} pgpbuilder Generates and encrypts MIME and SMTP messages
* @param {Object} mailreader Parses MIME messages received from IMAP
*/
var EmailDAO = function(keychain, pgp, devicestorage, pgpbuilder, mailreader) {
function Email(keychain, pgp, accountStore, pgpbuilder, mailreader, dialog) {
this._keychain = keychain;
this._pgp = pgp;
this._devicestorage = devicestorage;
this._devicestorage = accountStore;
this._pgpbuilder = pgpbuilder;
this._mailreader = mailreader;
};
this._dialog = dialog;
}
//
@ -71,7 +76,7 @@ var EmailDAO = function(keychain, pgp, devicestorage, pgpbuilder, mailreader) {
* @param {String} options.account.realname The user's id
* @param {Function} callback(error, keypair) Invoked with the keypair or error information when the email dao is initialized
*/
EmailDAO.prototype.init = function(options, callback) {
Email.prototype.init = function(options, callback) {
this._account = options.account;
this._account.busy = 0; // > 0 triggers the spinner
this._account.online = false;
@ -86,7 +91,7 @@ EmailDAO.prototype.init = function(options, callback) {
* @param {String} options.passphrase The passphrase to decrypt the private key
* @param {Function} callback(error) Invoked when the the keychain is unlocked or when an error occurred buring unlocking
*/
EmailDAO.prototype.unlock = function(options, callback) {
Email.prototype.unlock = function(options, callback) {
var self = this;
if (options.keypair) {
@ -202,7 +207,7 @@ EmailDAO.prototype.unlock = function(options, callback) {
* @param {Object} options.folder The folder to be opened
* @param {Function} callback(error) Invoked when the folder has been opened
*/
EmailDAO.prototype.openFolder = function(options, callback) {
Email.prototype.openFolder = function(options, callback) {
var self = this,
err;
@ -229,7 +234,7 @@ EmailDAO.prototype.openFolder = function(options, callback) {
* @param {Object} options.folder The folder to synchronize
* @param {Function} callback [description]
*/
EmailDAO.prototype.refreshFolder = function(options, callback) {
Email.prototype.refreshFolder = function(options, callback) {
var self = this,
folder = options.folder;
@ -289,7 +294,7 @@ EmailDAO.prototype.refreshFolder = function(options, callback) {
* @param {Object} options.folder The folder for which to fetch the message
* @param {Function} callback(error) Invoked when the message is persisted and added to folder.messages
*/
EmailDAO.prototype.fetchMessages = function(options, callback) {
Email.prototype.fetchMessages = function(options, callback) {
var self = this,
folder = options.folder;
@ -440,7 +445,7 @@ EmailDAO.prototype.fetchMessages = function(options, callback) {
* @param {Boolean} options.localOnly Indicated if the message should not be removed from IMAP
* @param {Function} callback(error) Invoked when the message was delete, or an error occurred
*/
EmailDAO.prototype.deleteMessage = function(options, callback) {
Email.prototype.deleteMessage = function(options, callback) {
var self = this,
folder = options.folder,
message = options.message;
@ -508,7 +513,7 @@ EmailDAO.prototype.deleteMessage = function(options, callback) {
* @param {[type]} options [description]
* @param {Function} callback [description]
*/
EmailDAO.prototype.setFlags = function(options, callback) {
Email.prototype.setFlags = function(options, callback) {
var self = this,
folder = options.folder,
message = options.message;
@ -600,7 +605,7 @@ EmailDAO.prototype.setFlags = function(options, callback) {
* @param {Object} options.message The message that should be moved
* @param {Function} callback(error) Invoked when the message was moved, or an error occurred
*/
EmailDAO.prototype.moveMessage = function(options, callback) {
Email.prototype.moveMessage = function(options, callback) {
var self = this,
folder = options.folder,
destination = options.destination,
@ -652,7 +657,7 @@ EmailDAO.prototype.moveMessage = function(options, callback) {
* @param {Object} options.folder The IMAP folder
* @param {Function} callback(error, message) Invoked when the message is streamed, or provides information if an error occurred
*/
EmailDAO.prototype.getBody = function(options, callback) {
Email.prototype.getBody = function(options, callback) {
var self = this,
message = options.message,
folder = options.folder;
@ -837,7 +842,7 @@ EmailDAO.prototype.getBody = function(options, callback) {
}
};
EmailDAO.prototype._checkSignatures = function(message, callback) {
Email.prototype._checkSignatures = function(message, callback) {
var self = this;
self._keychain.getReceiverPublicKey(message.from[0].address, function(err, senderPublicKey) {
@ -866,7 +871,7 @@ EmailDAO.prototype._checkSignatures = function(message, callback) {
* @param {Object} options.attachment The attachment body part to fetch and parse from IMAP
* @param {Function} callback(error, attachment) Invoked when the attachment body part was retrieved and parsed, or an error occurred
*/
EmailDAO.prototype.getAttachment = function(options, callback) {
Email.prototype.getAttachment = function(options, callback) {
var self = this,
attachment = options.attachment;
@ -896,7 +901,7 @@ EmailDAO.prototype.getAttachment = function(options, callback) {
* @param {Object} options.message The message
* @param {Function} callback(error, message)
*/
EmailDAO.prototype.decryptBody = function(options, callback) {
Email.prototype.decryptBody = function(options, callback) {
var self = this,
message = options.message;
@ -1016,7 +1021,7 @@ EmailDAO.prototype.decryptBody = function(options, callback) {
* @param {Object} options.email The message to be sent
* @param {Function} callback(error) Invoked when the message was sent, or an error occurred
*/
EmailDAO.prototype.sendEncrypted = function(options, callback) {
Email.prototype.sendEncrypted = function(options, callback) {
var self = this;
if (!self._account.online) {
@ -1056,7 +1061,7 @@ EmailDAO.prototype.sendEncrypted = function(options, callback) {
* @param {Object} options.email The message to be sent
* @param {Function} callback(error) Invoked when the message was sent, or an error occurred
*/
EmailDAO.prototype.sendPlaintext = function(options, callback) {
Email.prototype.sendPlaintext = function(options, callback) {
var self = this;
if (!self._account.online) {
@ -1097,7 +1102,7 @@ EmailDAO.prototype.sendPlaintext = function(options, callback) {
* @param {Object} options.email The message to be encrypted
* @param {Function} callback(error, message) Invoked when the message was encrypted, or an error occurred
*/
EmailDAO.prototype.encrypt = function(options, callback) {
Email.prototype.encrypt = function(options, callback) {
var self = this;
self.busy();
@ -1124,7 +1129,7 @@ EmailDAO.prototype.encrypt = function(options, callback) {
* @param {Object} options.pgpMailer The SMTP client used to send messages
* @param {Function} callback [description]
*/
EmailDAO.prototype.onConnect = function(options, callback) {
Email.prototype.onConnect = function(options, callback) {
var self = this;
self._account.loggingIn = true;
@ -1212,7 +1217,7 @@ EmailDAO.prototype.onConnect = function(options, callback) {
* This handler should be invoked when navigator.onLine === false.
* It will discard the imap client and pgp mailer
*/
EmailDAO.prototype.onDisconnect = function(callback) {
Email.prototype.onDisconnect = function(callback) {
var self = this;
// logout of imap-client
@ -1239,7 +1244,7 @@ EmailDAO.prototype.onDisconnect = function(callback) {
* @param {String} options.path The mailbox for which updates are available
* @param {Array} options.list Array containing update information. Number (uid) or mail with Object (uid and flags), respectively
*/
EmailDAO.prototype._onSyncUpdate = function(options) {
Email.prototype._onSyncUpdate = function(options) {
var self = this;
var folder = _.findWhere(self._account.folders, {
@ -1257,7 +1262,7 @@ EmailDAO.prototype._onSyncUpdate = function(options) {
folder: folder,
firstUid: Math.min.apply(null, options.list),
lastUid: Math.max.apply(null, options.list)
}, self.onError.bind(self));
}, self._dialog.error);
} else if (options.type === SYNC_TYPE_DELETED) {
// messages have been deleted, remove from local storage and memory
options.list.forEach(function(uid) {
@ -1273,7 +1278,7 @@ EmailDAO.prototype._onSyncUpdate = function(options) {
folder: folder,
message: message,
localOnly: true
}, self.onError.bind(self));
}, self._dialog.error);
});
} else if (options.type === SYNC_TYPE_MSGS) {
// NB! several possible reasons why this could be called.
@ -1300,7 +1305,7 @@ EmailDAO.prototype._onSyncUpdate = function(options) {
folder: folder,
message: message,
localOnly: true
}, self.onError.bind(self));
}, self._dialog.error);
});
}
};
@ -1319,7 +1324,7 @@ EmailDAO.prototype._onSyncUpdate = function(options) {
*
* @param {Function} callback Invoked when the folders are up to date
*/
EmailDAO.prototype._initFoldersFromDisk = function(callback) {
Email.prototype._initFoldersFromDisk = function(callback) {
var self = this;
self.busy(); // start the spinner
@ -1347,7 +1352,7 @@ EmailDAO.prototype._initFoldersFromDisk = function(callback) {
*
* @param {Function} callback Invoked when the folders are up to date
*/
EmailDAO.prototype._initFoldersFromImap = function(callback) {
Email.prototype._initFoldersFromImap = function(callback) {
var self = this;
self.busy(); // start the spinner
@ -1498,7 +1503,7 @@ EmailDAO.prototype._initFoldersFromImap = function(callback) {
*
* @param {Function} callback Invoked when the folders are filled with messages
*/
EmailDAO.prototype._initMessagesFromDisk = function(callback) {
Email.prototype._initMessagesFromDisk = function(callback) {
var self = this;
if (!self._account.folders || self._account.folders.length === 0) {
@ -1526,11 +1531,11 @@ EmailDAO.prototype._initMessagesFromDisk = function(callback) {
});
};
EmailDAO.prototype.busy = function() {
Email.prototype.busy = function() {
this._account.busy++;
};
EmailDAO.prototype.done = function() {
Email.prototype.done = function() {
if (this._account.busy > 0) {
this._account.busy--;
}
@ -1552,7 +1557,7 @@ EmailDAO.prototype.done = function() {
* @param {Number} options.unread Un-/Read flag
* @param {Number} options.answered Un-/Answered flag
*/
EmailDAO.prototype._imapMark = function(options, callback) {
Email.prototype._imapMark = function(options, callback) {
if (!this._account.online) {
callback({
errMsg: 'Client is currently offline!',
@ -1573,7 +1578,7 @@ EmailDAO.prototype._imapMark = function(options, callback) {
* @param {Number} options.uid The uid of the message
* @param {Function} callback(error) Callback with an error object in case something went wrong.
*/
EmailDAO.prototype._imapDeleteMessage = function(options, callback) {
Email.prototype._imapDeleteMessage = function(options, callback) {
if (!this._account.online) {
callback({
errMsg: 'Client is currently offline!',
@ -1611,7 +1616,7 @@ EmailDAO.prototype._imapDeleteMessage = function(options, callback) {
* @param {String} options.uid the message's uid
* @param {Function} callback (error) The callback when the message is moved
*/
EmailDAO.prototype._imapMoveMessage = function(options, callback) {
Email.prototype._imapMoveMessage = function(options, callback) {
this._imapClient.moveMessage({
path: options.folder.path,
destination: options.destination.path,
@ -1628,7 +1633,7 @@ EmailDAO.prototype._imapMoveMessage = function(options, callback) {
* @param {Number} options.lastUid The upper bound of the uid range (inclusive)
* @param {Function} callback (error, messages) The callback when the imap client is done fetching message metadata
*/
EmailDAO.prototype._imapListMessages = function(options, callback) {
Email.prototype._imapListMessages = function(options, callback) {
var self = this;
if (!this._account.online) {
@ -1650,7 +1655,7 @@ EmailDAO.prototype._imapListMessages = function(options, callback) {
* @param {String} options.message The rfc2822 compatible raw ASCII e-mail source
* @param {Function} callback (error) The callback when the imap client is done uploading
*/
EmailDAO.prototype._imapUploadMessage = function(options, callback) {
Email.prototype._imapUploadMessage = function(options, callback) {
this._imapClient.uploadMessage({
path: options.folder.path,
message: options.message
@ -1664,7 +1669,7 @@ EmailDAO.prototype._imapUploadMessage = function(options, callback) {
* @param {Object} options.bodyParts The message, as retrieved by _imapListMessages
* @param {Function} callback (error, message) The callback when the imap client is done streaming message text content
*/
EmailDAO.prototype._getBodyParts = function(options, callback) {
Email.prototype._getBodyParts = function(options, callback) {
var self = this;
if (!self._account.online) {
@ -1702,7 +1707,7 @@ EmailDAO.prototype._getBodyParts = function(options, callback) {
* @param {Object} options.uid A specific uid to look up locally in the folder
* @param {Function} callback(error, list) Invoked with the results of the query, or further information, if an error occurred
*/
EmailDAO.prototype._localListMessages = function(options, callback) {
Email.prototype._localListMessages = function(options, callback) {
var dbType = 'email_' + options.folder.path + (options.uid ? '_' + options.uid : '');
this._devicestorage.listItems(dbType, 0, null, callback);
};
@ -1714,7 +1719,7 @@ EmailDAO.prototype._localListMessages = function(options, callback) {
* @param {Array} options.messages The messages to store
* @param {Function} callback(error, list) Invoked with the results of the query, or further information, if an error occurred
*/
EmailDAO.prototype._localStoreMessages = function(options, callback) {
Email.prototype._localStoreMessages = function(options, callback) {
var dbType = 'email_' + options.folder.path;
this._devicestorage.storeList(options.emails, dbType, callback);
};
@ -1726,7 +1731,7 @@ EmailDAO.prototype._localStoreMessages = function(options, callback) {
* @param {Array} options.messages The messages to store
* @param {Function} callback(error, list) Invoked with the results of the query, or further information, if an error occurred
*/
EmailDAO.prototype._localDeleteMessage = function(options, callback) {
Email.prototype._localDeleteMessage = function(options, callback) {
var path = options.folder.path,
uid = options.uid,
id = options.id;
@ -1756,7 +1761,7 @@ EmailDAO.prototype._localDeleteMessage = function(options, callback) {
* @param {String} options.message The rfc2822 compatible raw ASCII e-mail source
* @param {Function} callback (error) The callback when the imap client is done uploading
*/
EmailDAO.prototype._uploadToSent = function(options, callback) {
Email.prototype._uploadToSent = function(options, callback) {
var self = this;
self.busy();
@ -1796,7 +1801,7 @@ EmailDAO.prototype._uploadToSent = function(options, callback) {
* @param {String} hostname The hostname to check
* @return {Boolean} true if upload can be ignored, otherwise false
*/
EmailDAO.prototype.checkIgnoreUploadOnSent = function(hostname) {
Email.prototype.checkIgnoreUploadOnSent = function(hostname) {
for (var i = 0; i < config.ignoreUploadOnSentDomains.length; i++) {
if (config.ignoreUploadOnSentDomains[i].test(hostname)) {
return true;
@ -1876,6 +1881,4 @@ function inlineExternalImages(message) {
return prefix + localSource + suffix;
});
}
module.exports = EmailDAO;
}

10
src/js/email/index.js Normal file
View File

@ -0,0 +1,10 @@
'use strict';
angular.module('woEmail', ['woAppConfig', 'woUtil', 'woServices', 'woCrypto']);
require('./mailreader');
require('./pgpbuilder');
require('./email');
require('./outbox');
require('./account');
require('./search');

View File

@ -0,0 +1,9 @@
'use strict';
var mailreader = require('mailreader');
var ngModule = angular.module('woEmail');
ngModule.factory('mailreader', function(appConfig) {
mailreader.startWorker(appConfig.config.workerPath + '/mailreader-parser-worker.min.js');
return mailreader;
});

View File

@ -1,5 +1,9 @@
'use strict';
var ngModule = angular.module('woEmail');
ngModule.service('outbox', Outbox);
module.exports = Outbox;
var util = require('crypto-lib').util,
config = require('../app-config').config,
outboxDb = 'email_OUTBOX';
@ -9,27 +13,27 @@ var util = require('crypto-lib').util,
* 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.
*/
var OutboxBO = function(emailDao, keychain, devicestorage) {
function Outbox(email, keychain, accountStore) {
/** @private */
this._emailDao = emailDao;
this._emailDao = email;
/** @private */
this._keychain = keychain;
/** @private */
this._devicestorage = devicestorage;
this._devicestorage = accountStore;
/**
* Semaphore-esque flag to avoid 'concurrent' calls to _processOutbox when the timeout fires, but a call is still in process.
* @private */
this._outboxBusy = false;
};
}
/**
* This function activates the periodic checking of the local device storage for pending mails.
* @param {Function} callback(error, pendingMailsCount) Callback that informs you about the count of pending mails.
*/
OutboxBO.prototype.startChecking = function(callback) {
Outbox.prototype.startChecking = function(callback) {
// remember global callback
this._onUpdate = callback;
// start periodic checking of outbox
@ -39,7 +43,7 @@ OutboxBO.prototype.startChecking = function(callback) {
/**
* Outbox stops the periodic checking of the local device storage for pending mails.
*/
OutboxBO.prototype.stopChecking = function() {
Outbox.prototype.stopChecking = function() {
if (!this._intervalId) {
return;
}
@ -53,7 +57,7 @@ OutboxBO.prototype.stopChecking = function() {
* @param {Object} mail The Email DTO
* @param {Function} callback Invoked when the object was encrypted and persisted to disk
*/
OutboxBO.prototype.put = function(mail, callback) {
Outbox.prototype.put = function(mail, callback) {
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
@ -133,7 +137,7 @@ OutboxBO.prototype.put = function(mail, callback) {
* Checks the local device storage for pending mails.
* @param {Function} callback(error, pendingMailsCount) Callback that informs you about the count of pending mails.
*/
OutboxBO.prototype._processOutbox = function(callback) {
Outbox.prototype._processOutbox = function(callback) {
var self = this,
unsentMails = 0;
@ -224,6 +228,4 @@ OutboxBO.prototype._processOutbox = function(callback) {
done();
});
}
};
module.exports = OutboxBO;
};

View File

@ -0,0 +1,8 @@
'use strict';
var PgpBuilder = require('pgpbuilder');
var ngModule = angular.module('woEmail');
ngModule.factory('pgpbuilder', function() {
return new PgpBuilder();
});

80
src/js/email/search.js Normal file
View File

@ -0,0 +1,80 @@
'use strict';
var ngModule = angular.module('woEmail');
ngModule.service('search', Search);
module.exports = Search;
function Search() {}
/**
* Do full text search on messages. Parse meta data first.
* @param {Array} messages The messages to be filtered
* @param {String} query The text query used to filter messages
* @return {Array} The filtered messages
*/
Search.prototype.filter = function(messages, query) {
// don't filter on empty query
if (!query) {
return messages;
}
// escape search string
query = query.replace(/([.*+?^${}()|\[\]\/\\])/g, "\\$1");
// compare all strings (case insensitive)
var regex = new RegExp(query, 'i');
function contains(input) {
if (!input) {
return false;
}
return regex.test(input);
}
function checkAddresses(header) {
if (!header || !header.length) {
return false;
}
for (var i = 0; i < header.length; i++) {
if (contains(header[i].name) || contains(header[i].address)) {
return true;
}
}
return false;
}
/**
* Filter meta data first and then only look at plaintext and decrypted message bodies
*/
function matchMetaDataFirst(m) {
// compare subject
if (contains(m.subject)) {
return true;
}
// compares address headers
if (checkAddresses(m.from) || checkAddresses(m.to) || checkAddresses(m.cc) || checkAddresses(m.bcc)) {
return true;
}
// compare plaintext body
if (m.body && !m.encrypted && contains(m.body)) {
return true;
}
// compare decrypted body
if (m.body && m.encrypted && m.decrypted && contains(m.body)) {
return true;
}
// compare plaintex html body
if (m.html && !m.encrypted && contains(m.html)) {
return true;
}
// compare decrypted html body
if (m.html && m.encrypted && m.decrypted && contains(m.html)) {
return true;
}
return false;
}
// user native js Array.filter
return messages.filter(matchMetaDataFirst);
};

View File

@ -1,8 +1,12 @@
'use strict';
var AdminDAO = function(restDao) {
this._restDao = restDao;
};
var ngModule = angular.module('woServices');
ngModule.service('admin', Admin);
module.exports = Admin;
function Admin(adminRestDao) {
this._restDao = adminRestDao;
}
/**
* Create a new email account.
@ -11,7 +15,7 @@ var AdminDAO = function(restDao) {
* @param {String} options.phone The user's mobile phone number (required for verification and password reset).
* @param {Function} callback(error)
*/
AdminDAO.prototype.createUser = function(options, callback) {
Admin.prototype.createUser = function(options, callback) {
var uri;
if (!options.emailAddress || !options.password || !options.phone) {
@ -39,7 +43,7 @@ AdminDAO.prototype.createUser = function(options, callback) {
* @param {String} options.token The validation token.
* @param {Function} callback(error)
*/
AdminDAO.prototype.validateUser = function(options, callback) {
Admin.prototype.validateUser = function(options, callback) {
var uri;
if (!options.emailAddress || !options.token) {
@ -56,6 +60,4 @@ AdminDAO.prototype.validateUser = function(options, callback) {
callback(new Error('Validation failed!'));
}
});
};
module.exports = AdminDAO;
};

View File

@ -1,5 +1,9 @@
'use strict';
var ngModule = angular.module('woServices');
ngModule.service('auth', Auth);
module.exports = Auth;
var axe = require('axe-logger'),
cfg = require('../app-config').config,
str = require('../app-config').string;
@ -21,10 +25,24 @@ var SMTP_DB_KEY = 'smtp';
* auth.getCredentials(...); // called to gather all the information to connect to IMAP/SMTP,
* username, password / oauth token, IMAP/SMTP server host names, ...
*/
var Auth = function(appConfigStore, oauth, pgp) {
function Auth(appConfigStore, oauth, pgp) {
this._appConfigStore = appConfigStore;
this._oauth = oauth;
this._pgp = pgp;
}
/**
* Initialize the service
*/
Auth.prototype.init = function() {
this._initialized = true;
};
/**
* Check if the service has been initialized.
*/
Auth.prototype.isInitialized = function() {
return this._initialized;
};
/**
@ -439,6 +457,4 @@ Auth.prototype.logout = function(callback) {
callback();
});
};
module.exports = Auth;
};

View File

@ -1,15 +1,38 @@
/**
* High level storage api that handles all persistence on the device.
*/
'use strict';
var DeviceStorageDAO = function(localDbDao) {
this._localDbDao = localDbDao;
};
var ngModule = angular.module('woServices');
DeviceStorageDAO.prototype.init = function(emailAddress, callback) {
this._localDbDao.init(emailAddress, callback);
// expose an instance with the static dbName 'app-config' to store configuration data
ngModule.factory('appConfigStore', function(appConfigLawnchair) {
var deviceStorage = new DeviceStorage(appConfigLawnchair);
deviceStorage.init('app-config');
return deviceStorage;
});
// expose a singleton instance of DeviceStorage called 'accountStore' to persist user data
ngModule.factory('accountStore', function(accountLawnchair) {
return new DeviceStorage(accountLawnchair);
});
module.exports = DeviceStorage;
//
// Implementation
//
/**
* High level storage api that handles all persistence of a user's data on the device.
*/
function DeviceStorage(lawnchairDAO) {
this._lawnchairDAO = lawnchairDAO;
}
/**
* Initialize the lawnchair database
* @param {String} dbName The name of the database
*/
DeviceStorage.prototype.init = function(dbName) {
this._lawnchairDAO.init(dbName);
};
/**
@ -17,7 +40,7 @@ DeviceStorageDAO.prototype.init = function(emailAddress, callback) {
* @param list [Array] The list of items to be persisted
* @param type [String] The type of item to be persisted e.g. 'email'
*/
DeviceStorageDAO.prototype.storeList = function(list, type, callback) {
DeviceStorage.prototype.storeList = function(list, type, callback) {
var key, items = [];
// nothing to store
@ -43,14 +66,14 @@ DeviceStorageDAO.prototype.storeList = function(list, type, callback) {
});
});
this._localDbDao.batch(items, callback);
this._lawnchairDAO.batch(items, callback);
};
/**
* Deletes items of a certain type from storage
*/
DeviceStorageDAO.prototype.removeList = function(type, callback) {
this._localDbDao.removeList(type, callback);
DeviceStorage.prototype.removeList = function(type, callback) {
this._lawnchairDAO.removeList(type, callback);
};
/**
@ -59,16 +82,16 @@ DeviceStorageDAO.prototype.removeList = function(type, callback) {
* @param offset [Number] The offset of items to fetch (0 is the last stored item)
* @param num [Number] The number of items to fetch (null means fetch all)
*/
DeviceStorageDAO.prototype.listItems = function(type, offset, num, callback) {
DeviceStorage.prototype.listItems = function(type, offset, num, callback) {
// fetch all items of a certain type from the data-store
this._localDbDao.list(type, offset, num, callback);
this._lawnchairDAO.list(type, offset, num, callback);
};
/**
* Clear the whole device data-store
*/
DeviceStorageDAO.prototype.clear = function(callback) {
this._localDbDao.clear(callback);
DeviceStorage.prototype.clear = function(callback) {
this._lawnchairDAO.clear(callback);
};
//
@ -88,6 +111,4 @@ function createKey(i, type) {
}
return key;
}
module.exports = DeviceStorageDAO;
}

16
src/js/service/index.js Normal file
View File

@ -0,0 +1,16 @@
'use strict';
angular.module('woServices', ['woAppConfig', 'woUtil', 'woCrypto']);
require('./rest');
require('./invitation');
require('./mail-config');
require('./newsletter');
require('./oauth');
require('./privatekey');
require('./publickey');
require('./admin');
require('./lawnchair');
require('./devicestorage');
require('./auth');
require('./keychain');

View File

@ -1,20 +1,24 @@
'use strict';
var ngModule = angular.module('woServices');
ngModule.service('invitation', Invitation);
module.exports = Invitation;
/**
* The InvitationDAO is a high level Data Access Object that access the invitation service REST endpoint.
* 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
*/
var InvitationDAO = function(restDao) {
this._restDao = restDao;
};
function Invitation(invitationRestDao) {
this._restDao = invitationRestDao;
}
//
// Constants
//
InvitationDAO.INVITE_MISSING = 1;
InvitationDAO.INVITE_PENDING = 2;
InvitationDAO.INVITE_SUCCESS = 4;
Invitation.INVITE_MISSING = 1;
Invitation.INVITE_PENDING = 2;
Invitation.INVITE_SUCCESS = 4;
//
// API
@ -26,7 +30,7 @@ InvitationDAO.INVITE_SUCCESS = 4;
* @param {String} options.sender User ID of the sender
* @param {Function} callback(error, status) Returns information if the invitation worked (INVITE_SUCCESS), if an invitation is already pendin (INVITE_PENDING), or information if an error occurred.
*/
InvitationDAO.prototype.invite = function(options, callback) {
Invitation.prototype.invite = function(options, callback) {
if (typeof options !== 'object' || typeof options.recipient !== 'string' || typeof options.recipient !== 'string') {
callback({
errMsg: 'erroneous usage of api: incorrect parameters!'
@ -44,10 +48,10 @@ InvitationDAO.prototype.invite = function(options, callback) {
}
if (status === 201) {
callback(null, InvitationDAO.INVITE_SUCCESS);
callback(null, Invitation.INVITE_SUCCESS);
return;
} else if (status === 304) {
callback(null, InvitationDAO.INVITE_PENDING);
callback(null, Invitation.INVITE_PENDING);
return;
}
@ -55,6 +59,4 @@ InvitationDAO.prototype.invite = function(options, callback) {
errMsg: 'unexpected invitation state'
});
}
};
module.exports = InvitationDAO;
};

View File

@ -1,36 +1,60 @@
/**
* A high-level Data-Access Api for handling Keypair synchronization
* between the cloud service and the device's local storage
*/
'use strict';
var util = require('crypto-lib').util,
config = require('../app-config').config;
var ngModule = angular.module('woServices');
ngModule.service('keychain', Keychain);
module.exports = Keychain;
var util = require('crypto-lib').util;
var DB_PUBLICKEY = 'publickey',
DB_PRIVATEKEY = 'privatekey',
DB_DEVICENAME = 'devicename',
DB_DEVICE_SECRET = 'devicesecret';
var KeychainDAO = function(localDbDao, publicKeyDao, privateKeyDao, crypto, pgp) {
this._localDbDao = localDbDao;
this._publicKeyDao = publicKeyDao;
this._privateKeyDao = privateKeyDao;
/**
* A high-level Data-Access Api for handling Keypair synchronization
* between the cloud service and the device's local storage
*/
function Keychain(accountLawnchair, publicKey, privateKey, crypto, pgp, dialog, appConfig) {
this._lawnchairDAO = accountLawnchair;
this._publicKeyDao = publicKey;
this._privateKeyDao = privateKey;
this._crypto = crypto;
this._pgp = pgp;
};
this._dialog = dialog;
this._appConfig = appConfig;
}
//
// Public key functions
//
/**
* Display confirmation dialog to request a public key update
* @param {Object} params.newKey The user's updated public key object
* @param {String} params.userId The user's email address
*/
Keychain.prototype.requestPermissionForKeyUpdate = function(params, callback) {
var str = this._appConfig.string;
var message = params.newKey ? str.updatePublicKeyMsgNewKey : str.updatePublicKeyMsgRemovedKey;
message = message.replace('{0}', params.userId);
this._dialog.confirm({
title: str.updatePublicKeyTitle,
message: message,
positiveBtnStr: str.updatePublicKeyPosBtn,
negativeBtnStr: str.updatePublicKeyNegBtn,
showNegativeBtn: true,
callback: callback
});
};
/**
* Verifies the public key of a user o nthe public key store
* @param {String} uuid The uuid to verify the key
* @param {Function} callback(error) Callback with an optional error object when the verification is done. If the was an error, the error object contains the information for it.
*/
KeychainDAO.prototype.verifyPublicKey = function(uuid, callback) {
Keychain.prototype.verifyPublicKey = function(uuid, callback) {
this._publicKeyDao.verify(uuid, callback);
};
@ -40,7 +64,7 @@ KeychainDAO.prototype.verifyPublicKey = function(uuid, callback) {
* @param ids [Array] the key ids as [{_id, userId}]
* @return [PublicKeyCollection] The requiested public keys
*/
KeychainDAO.prototype.getPublicKeys = function(ids, callback) {
Keychain.prototype.getPublicKeys = function(ids, callback) {
var self = this,
after, already, pubkeys = [];
@ -85,7 +109,7 @@ KeychainDAO.prototype.getPublicKeys = function(ids, callback) {
* @param {String} options.overridePermission (optional) Indicates if the update should happen automatically (true) or with the user being queried (false). Defaults to false
* @param {Function} callback(error, key) Invoked when the key has been updated or an error occurred
*/
KeychainDAO.prototype.refreshKeyForUserId = function(options, callback) {
Keychain.prototype.refreshKeyForUserId = function(options, callback) {
var self = this,
userId = options.userId,
overridePermission = options.overridePermission;
@ -189,11 +213,11 @@ KeychainDAO.prototype.refreshKeyForUserId = function(options, callback) {
* Look up a reveiver's public key by user id
* @param userId [String] the receiver's email address
*/
KeychainDAO.prototype.getReceiverPublicKey = function(userId, callback) {
Keychain.prototype.getReceiverPublicKey = function(userId, callback) {
var self = this;
// search local keyring for public key
self._localDbDao.list(DB_PUBLICKEY, 0, null, function(err, allPubkeys) {
self._lawnchairDAO.list(DB_PUBLICKEY, 0, null, function(err, allPubkeys) {
if (err) {
callback(err);
return;
@ -266,13 +290,13 @@ KeychainDAO.prototype.getReceiverPublicKey = function(userId, callback) {
* @param {String} deviceName The device name
* @param {Function} callback(error)
*/
KeychainDAO.prototype.setDeviceName = function(deviceName, callback) {
Keychain.prototype.setDeviceName = function(deviceName, callback) {
if (!deviceName) {
callback(new Error('Please set a device name!'));
return;
}
this._localDbDao.persist(DB_DEVICENAME, deviceName, callback);
this._lawnchairDAO.persist(DB_DEVICENAME, deviceName, callback);
};
/**
@ -280,9 +304,9 @@ KeychainDAO.prototype.setDeviceName = function(deviceName, callback) {
* @param {Function} callback(error, deviceName)
* @return {String} The device name
*/
KeychainDAO.prototype.getDeviceName = function(callback) {
Keychain.prototype.getDeviceName = function(callback) {
// check if deviceName is already persisted in storage
this._localDbDao.read(DB_DEVICENAME, function(err, deviceName) {
this._lawnchairDAO.read(DB_DEVICENAME, function(err, deviceName) {
if (err) {
callback(err);
return;
@ -301,11 +325,12 @@ KeychainDAO.prototype.getDeviceName = function(callback) {
* Geneate a device specific key and secret to authenticate to the private key service.
* @param {Function} callback(error, deviceSecret:[base64 encoded string])
*/
KeychainDAO.prototype.getDeviceSecret = function(callback) {
var self = this;
Keychain.prototype.getDeviceSecret = function(callback) {
var self = this,
config = self._appConfig.config;
// generate random deviceSecret or get from storage
self._localDbDao.read(DB_DEVICE_SECRET, function(err, storedDevSecret) {
self._lawnchairDAO.read(DB_DEVICE_SECRET, function(err, storedDevSecret) {
if (err) {
callback(err);
return;
@ -320,7 +345,7 @@ KeychainDAO.prototype.getDeviceSecret = function(callback) {
// generate random deviceSecret
var deviceSecret = util.random(config.symKeySize);
// persist deviceSecret to local storage (in plaintext)
self._localDbDao.persist(DB_DEVICE_SECRET, deviceSecret, function(err) {
self._lawnchairDAO.persist(DB_DEVICE_SECRET, deviceSecret, function(err) {
if (err) {
callback(err);
return;
@ -336,9 +361,10 @@ KeychainDAO.prototype.getDeviceSecret = function(callback) {
* @param {String} options.userId The user's email address
* @param {Function} callback(error)
*/
KeychainDAO.prototype.registerDevice = function(options, callback) {
Keychain.prototype.registerDevice = function(options, callback) {
var self = this,
devName;
devName,
config = self._appConfig.config;
// check if deviceName is already persisted in storage
self.getDeviceName(function(err, deviceName) {
@ -435,9 +461,10 @@ KeychainDAO.prototype.registerDevice = function(options, callback) {
* @param {Function} callback(error, authSessionKey)
* @return {Object} {sessionId:String, sessionKey:[base64 encoded]}
*/
KeychainDAO.prototype._authenticateToPrivateKeyServer = function(userId, callback) {
Keychain.prototype._authenticateToPrivateKeyServer = function(userId, callback) {
var self = this,
sessionId;
sessionId,
config = self._appConfig.config;
// request auth session key required for upload
self._privateKeyDao.requestAuthSessionKey({
@ -552,8 +579,9 @@ KeychainDAO.prototype._authenticateToPrivateKeyServer = function(userId, callbac
* @param {String} options.code The randomly generated or self selected code used to derive the key for the encryption of the private PGP key
* @param {Function} callback(error)
*/
KeychainDAO.prototype.uploadPrivateKey = function(options, callback) {
Keychain.prototype.uploadPrivateKey = function(options, callback) {
var self = this,
config = self._appConfig.config,
keySize = config.symKeySize,
salt;
@ -646,7 +674,7 @@ KeychainDAO.prototype.uploadPrivateKey = function(options, callback) {
* @param {String} options.keyId The private PGP key id
* @param {Function} callback(error)
*/
KeychainDAO.prototype.requestPrivateKeyDownload = function(options, callback) {
Keychain.prototype.requestPrivateKeyDownload = function(options, callback) {
this._privateKeyDao.requestDownload(options, callback);
};
@ -656,7 +684,7 @@ KeychainDAO.prototype.requestPrivateKeyDownload = function(options, callback) {
* @param {String} options.keyId The private PGP key id
* @param {Function} callback(error)
*/
KeychainDAO.prototype.hasPrivateKey = function(options, callback) {
Keychain.prototype.hasPrivateKey = function(options, callback) {
this._privateKeyDao.hasPrivateKey(options, callback);
};
@ -667,7 +695,7 @@ KeychainDAO.prototype.hasPrivateKey = function(options, callback) {
* @param {String} options.recoveryToken The recovery token acquired via email/sms from the key server
* @param {Function} callback(error, encryptedPrivateKey)
*/
KeychainDAO.prototype.downloadPrivateKey = function(options, callback) {
Keychain.prototype.downloadPrivateKey = function(options, callback) {
this._privateKeyDao.download(options, callback);
};
@ -681,10 +709,11 @@ KeychainDAO.prototype.downloadPrivateKey = function(options, callback) {
* @param {String} options.iv The iv used to encrypt the private PGP key
* @param {Function} callback(error, keyObject)
*/
KeychainDAO.prototype.decryptAndStorePrivateKeyLocally = function(options, callback) {
Keychain.prototype.decryptAndStorePrivateKeyLocally = function(options, callback) {
var self = this,
code = options.code,
salt = options.salt,
config = self._appConfig.config,
keySize = config.symKeySize;
if (!options._id || !options.userId || !options.code || !options.salt || !options.encryptedPrivateKey || !options.iv) {
@ -756,11 +785,11 @@ KeychainDAO.prototype.decryptAndStorePrivateKeyLocally = function(options, callb
* If no key pair exists, null is returned.
* return [Object] The user's key pair {publicKey, privateKey}
*/
KeychainDAO.prototype.getUserKeyPair = function(userId, callback) {
Keychain.prototype.getUserKeyPair = function(userId, callback) {
var self = this;
// search for user's public key locally
self._localDbDao.list(DB_PUBLICKEY, 0, null, function(err, allPubkeys) {
self._lawnchairDAO.list(DB_PUBLICKEY, 0, null, function(err, allPubkeys) {
if (err) {
callback(err);
return;
@ -833,7 +862,7 @@ KeychainDAO.prototype.getUserKeyPair = function(userId, callback) {
* locally and in the cloud and persist arccordingly
* @param [Object] The user's key pair {publicKey, privateKey}
*/
KeychainDAO.prototype.putUserKeyPair = function(keypair, callback) {
Keychain.prototype.putUserKeyPair = function(keypair, callback) {
var self = this;
// validate input
@ -872,7 +901,7 @@ KeychainDAO.prototype.putUserKeyPair = function(keypair, callback) {
// Helper functions
//
KeychainDAO.prototype.lookupPublicKey = function(id, callback) {
Keychain.prototype.lookupPublicKey = function(id, callback) {
var self = this;
if (!id) {
@ -883,7 +912,7 @@ KeychainDAO.prototype.lookupPublicKey = function(id, callback) {
}
// lookup in local storage
self._localDbDao.read(DB_PUBLICKEY + '_' + id, function(err, pubkey) {
self._lawnchairDAO.read(DB_PUBLICKEY + '_' + id, function(err, pubkey) {
if (err) {
callback(err);
return;
@ -917,30 +946,28 @@ KeychainDAO.prototype.lookupPublicKey = function(id, callback) {
/**
* List all the locally stored public keys
*/
KeychainDAO.prototype.listLocalPublicKeys = function(callback) {
Keychain.prototype.listLocalPublicKeys = function(callback) {
// search local keyring for public key
this._localDbDao.list(DB_PUBLICKEY, 0, null, callback);
this._lawnchairDAO.list(DB_PUBLICKEY, 0, null, callback);
};
KeychainDAO.prototype.removeLocalPublicKey = function(id, callback) {
this._localDbDao.remove(DB_PUBLICKEY + '_' + id, callback);
Keychain.prototype.removeLocalPublicKey = function(id, callback) {
this._lawnchairDAO.remove(DB_PUBLICKEY + '_' + id, callback);
};
KeychainDAO.prototype.lookupPrivateKey = function(id, callback) {
Keychain.prototype.lookupPrivateKey = function(id, callback) {
// lookup in local storage
this._localDbDao.read(DB_PRIVATEKEY + '_' + id, callback);
this._lawnchairDAO.read(DB_PRIVATEKEY + '_' + id, callback);
};
KeychainDAO.prototype.saveLocalPublicKey = function(pubkey, callback) {
Keychain.prototype.saveLocalPublicKey = function(pubkey, callback) {
// persist public key (email, _id)
var pkLookupKey = DB_PUBLICKEY + '_' + pubkey._id;
this._localDbDao.persist(pkLookupKey, pubkey, callback);
this._lawnchairDAO.persist(pkLookupKey, pubkey, callback);
};
KeychainDAO.prototype.saveLocalPrivateKey = function(privkey, callback) {
Keychain.prototype.saveLocalPrivateKey = function(privkey, callback) {
// persist private key (email, _id)
var prkLookupKey = DB_PRIVATEKEY + '_' + privkey._id;
this._localDbDao.persist(prkLookupKey, privkey, callback);
};
module.exports = KeychainDAO;
this._lawnchairDAO.persist(prkLookupKey, privkey, callback);
};

View File

@ -1,30 +1,26 @@
'use strict';
var ngModule = angular.module('woServices');
ngModule.service('appConfigLawnchair', LawnchairDAO);
ngModule.service('accountLawnchair', LawnchairDAO);
module.exports = LawnchairDAO;
/**
* Handles generic caching of JSON objects in a lawnchair adapter
*/
function LawnchairDAO() {}
'use strict';
var LawnchairDAO = function() {};
LawnchairDAO.prototype.init = function(dbName, callback) {
/**
* Initialize the lawnchair database
* @param {String} dbName The name of the database
*/
LawnchairDAO.prototype.init = function(dbName) {
if (!dbName) {
callback({
errMsg: 'Lawnchair DB name must be specified!'
});
return;
throw new Error('Lawnchair DB name must be specified!');
}
this._db = new Lawnchair({
name: dbName
}, function(lc) {
if (!lc) {
callback({
errMsg: 'Lawnchair init failed!'
});
return;
}
callback();
});
};
@ -212,5 +208,3 @@ LawnchairDAO.prototype.removeList = function(type, callback) {
LawnchairDAO.prototype.clear = function(callback) {
this._db.nuke(callback);
};
module.exports = LawnchairDAO;

View File

@ -2,12 +2,12 @@
var ngModule = angular.module('woServices');
ngModule.service('mailConfig', MailConfig);
module.exports = MailConfig;
var cfg = require('../app-config').config;
function MailConfig($http, $q) {
function MailConfig($http, $q, appConfig) {
this._http = $http;
this._q = $q;
this._appConfig = appConfig;
}
/**
@ -20,7 +20,7 @@ MailConfig.prototype.get = function(emailAddress) {
});
}
var url = cfg.settingsUrl + emailAddress.split('@')[1];
var url = this._appConfig.config.settingsUrl + emailAddress.split('@')[1];
return this._http.get(url).then(function(res) {
return res.data;
});

View File

@ -1,7 +1,8 @@
'use strict';
var ngModule = angular.module('woServices', []);
var ngModule = angular.module('woServices');
ngModule.service('newsletter', Newsletter);
module.exports = Newsletter;
function Newsletter($q) {
this._q = $q;

View File

@ -1,8 +1,12 @@
'use strict';
var OAuth = function(googleApi) {
this._googleApi = googleApi;
};
var ngModule = angular.module('woServices');
ngModule.service('oauth', OAuth);
module.exports = OAuth;
function OAuth(oauthRestDao) {
this._googleApi = oauthRestDao;
}
/**
* Check if chrome.identity api is supported
@ -94,6 +98,4 @@ OAuth.prototype.queryEmailAddress = function(token, callback) {
callback(null, info.email);
});
};
module.exports = OAuth;
};

View File

@ -1,8 +1,12 @@
'use strict';
var PrivateKeyDAO = function(restDao) {
this._restDao = restDao;
};
var ngModule = angular.module('woServices');
ngModule.service('privateKey', PrivateKey);
module.exports = PrivateKey;
function PrivateKey(privateKeyRestDao) {
this._restDao = privateKeyRestDao;
}
//
// Device registration functions
@ -15,7 +19,7 @@ var PrivateKeyDAO = function(restDao) {
* @param {Function} callback(error, regSessionKey)
* @return {Object} {encryptedRegSessionKey:[base64]}
*/
PrivateKeyDAO.prototype.requestDeviceRegistration = function(options, callback) {
PrivateKey.prototype.requestDeviceRegistration = function(options, callback) {
var uri;
if (!options.userId || !options.deviceName) {
@ -35,7 +39,7 @@ PrivateKeyDAO.prototype.requestDeviceRegistration = function(options, callback)
* @param {String} options.iv The iv used for encryption
* @param {Function} callback(error)
*/
PrivateKeyDAO.prototype.uploadDeviceSecret = function(options, callback) {
PrivateKey.prototype.uploadDeviceSecret = function(options, callback) {
var uri;
if (!options.userId || !options.deviceName || !options.encryptedDeviceSecret || !options.iv) {
@ -57,7 +61,7 @@ PrivateKeyDAO.prototype.uploadDeviceSecret = function(options, callback) {
* @param {Function} callback(error, authSessionKey)
* @return {Object} {sessionId, encryptedAuthSessionKey:[base64 encoded], encryptedChallenge:[base64 encoded]}
*/
PrivateKeyDAO.prototype.requestAuthSessionKey = function(options, callback) {
PrivateKey.prototype.requestAuthSessionKey = function(options, callback) {
var uri;
if (!options.userId) {
@ -77,7 +81,7 @@ PrivateKeyDAO.prototype.requestAuthSessionKey = function(options, callback) {
* @param {String} options.iv The iv used for encryption
* @param {Function} callback(error)
*/
PrivateKeyDAO.prototype.verifyAuthentication = function(options, callback) {
PrivateKey.prototype.verifyAuthentication = function(options, callback) {
var uri;
if (!options.userId || !options.sessionId || !options.encryptedChallenge || !options.encryptedDeviceSecret || !options.iv) {
@ -97,7 +101,7 @@ PrivateKeyDAO.prototype.verifyAuthentication = function(options, callback) {
* @param {String} options.sessionId The session id
* @param {Function} callback(error)
*/
PrivateKeyDAO.prototype.upload = function(options, callback) {
PrivateKey.prototype.upload = function(options, callback) {
var uri;
if (!options._id || !options.userId || !options.encryptedPrivateKey || !options.sessionId || !options.salt || !options.iv) {
@ -116,7 +120,7 @@ PrivateKeyDAO.prototype.upload = function(options, callback) {
* @param {Function} callback(error, found)
* @return {Boolean} whether the key was found on the server or not.
*/
PrivateKeyDAO.prototype.hasPrivateKey = function(options, callback) {
PrivateKey.prototype.hasPrivateKey = function(options, callback) {
if (!options.userId || !options.keyId) {
callback(new Error('Incomplete arguments!'));
return;
@ -147,7 +151,7 @@ PrivateKeyDAO.prototype.hasPrivateKey = function(options, callback) {
* @param {Function} callback(error, found)
* @return {Boolean} whether the key was found on the server or not.
*/
PrivateKeyDAO.prototype.requestDownload = function(options, callback) {
PrivateKey.prototype.requestDownload = function(options, callback) {
if (!options.userId || !options.keyId) {
callback(new Error('Incomplete arguments!'));
return;
@ -179,7 +183,7 @@ PrivateKeyDAO.prototype.requestDownload = function(options, callback) {
* @param {Function} callback(error, encryptedPrivateKey)
* @return {Object} {_id:[hex encoded capital 16 char key id], encryptedPrivateKey:[base64 encoded], encryptedUserId: [base64 encoded]}
*/
PrivateKeyDAO.prototype.download = function(options, callback) {
PrivateKey.prototype.download = function(options, callback) {
var uri;
if (!options.userId || !options.keyId || !options.recoveryToken) {
@ -191,6 +195,4 @@ PrivateKeyDAO.prototype.download = function(options, callback) {
this._restDao.get({
uri: uri
}, callback);
};
module.exports = PrivateKeyDAO;
};

View File

@ -1,13 +1,17 @@
'use strict';
var PublicKeyDAO = function(restDao) {
this._restDao = restDao;
};
var ngModule = angular.module('woServices');
ngModule.service('publicKey', PublicKey);
module.exports = PublicKey;
function PublicKey(publicKeyRestDao) {
this._restDao = publicKeyRestDao;
}
/**
* Verify the public key behind the given uuid
*/
PublicKeyDAO.prototype.verify = function(uuid, callback) {
PublicKey.prototype.verify = function(uuid, callback) {
var uri = '/verify/' + uuid;
this._restDao.get({
@ -27,7 +31,7 @@ PublicKeyDAO.prototype.verify = function(uuid, callback) {
/**
* Find the user's corresponding public key
*/
PublicKeyDAO.prototype.get = function(keyId, callback) {
PublicKey.prototype.get = function(keyId, callback) {
var uri = '/publickey/key/' + keyId;
this._restDao.get({
@ -50,7 +54,7 @@ PublicKeyDAO.prototype.get = function(keyId, callback) {
/**
* Find the user's corresponding public key by email
*/
PublicKeyDAO.prototype.getByUserId = function(userId, callback) {
PublicKey.prototype.getByUserId = function(userId, callback) {
var uri = '/publickey/user/' + userId;
this._restDao.get({
@ -87,7 +91,7 @@ PublicKeyDAO.prototype.getByUserId = function(userId, callback) {
/**
* Persist the user's publc key
*/
PublicKeyDAO.prototype.put = function(pubkey, callback) {
PublicKey.prototype.put = function(pubkey, callback) {
var uri = '/publickey/user/' + pubkey.userId + '/key/' + pubkey._id;
this._restDao.put(pubkey, uri, callback);
};
@ -95,9 +99,7 @@ PublicKeyDAO.prototype.put = function(pubkey, callback) {
/**
* Delete the public key from the cloud storage service
*/
PublicKeyDAO.prototype.remove = function(keyId, callback) {
PublicKey.prototype.remove = function(keyId, callback) {
var uri = '/publickey/key/' + keyId;
this._restDao.remove(uri, callback);
};
module.exports = PublicKeyDAO;
};

View File

@ -1,13 +1,52 @@
'use strict';
var config = require('../app-config').config;
var ngModule = angular.module('woServices');
var RestDAO = function(baseUri) {
if (baseUri) {
this._baseUri = baseUri;
} else {
this._baseUri = config.cloudUrl;
}
// rest dao for use in the public key service
ngModule.factory('publicKeyRestDao', function(appConfig) {
var dao = new RestDAO();
dao.setBaseUri(appConfig.config.cloudUrl);
return dao;
});
// rest dao for use in the private key service
ngModule.factory('privateKeyRestDao', function(appConfig) {
var dao = new RestDAO();
dao.setBaseUri(appConfig.config.privkeyServerUrl);
return dao;
});
// rest dao for use in the invitation service
ngModule.factory('invitationRestDao', function(appConfig) {
var dao = new RestDAO();
dao.setBaseUri(appConfig.config.cloudUrl);
return dao;
});
// rest dao for use in the admin service
ngModule.factory('adminRestDao', function(appConfig) {
var dao = new RestDAO();
dao.setBaseUri(appConfig.config.adminUrl);
return dao;
});
// rest dao for use in the oauth service
ngModule.factory('oauthRestDao', function() {
var dao = new RestDAO();
dao.setBaseUri('https://www.googleapis.com');
return dao;
});
module.exports = RestDAO;
function RestDAO() {}
/**
* Set the REST DAO's base url
* @param {String} baseUri The base url e.g. https://api.example.com
*/
RestDAO.prototype.setBaseUri = function(baseUri) {
this._baseUri = baseUri;
};
/**
@ -116,6 +155,4 @@ RestDAO.prototype._processRequest = function(options, callback) {
};
xhr.send(options.payload ? JSON.stringify(options.payload) : undefined);
};
module.exports = RestDAO;
};

8
src/js/util/axe.js Normal file
View File

@ -0,0 +1,8 @@
'use strict';
var axe = require('axe-logger');
var ngModule = angular.module('woUtil');
ngModule.factory('axe', function() {
return axe;
});

View File

@ -1,9 +1,10 @@
'use strict';
var ngModule = angular.module('woUtil');
ngModule.service('connectionDoctor', ConnectionDoctor);
module.exports = ConnectionDoctor;
var TCPSocket = require('tcp-socket'),
appConfig = require('../app-config'),
cfg = appConfig.config,
strings = appConfig.string,
ImapClient = require('imap-client'),
SmtpClient = require('wo-smtpclient');
@ -14,8 +15,10 @@ var TCPSocket = require('tcp-socket'),
*
* @constructor
*/
var ConnectionDoctor = function() {};
function ConnectionDoctor(appConfig) {
this._appConfig = appConfig;
this._workerPath = appConfig.config.workerPath + '/tcp-socket-tls-worker.min.js';
}
//
// Error codes
@ -29,9 +32,6 @@ var AUTH_REJECTED = ConnectionDoctor.AUTH_REJECTED = 46;
var NO_INBOX = ConnectionDoctor.NO_INBOX = 47;
var GENERIC_ERROR = ConnectionDoctor.GENERIC_ERROR = 48;
var WORKER_PATH = cfg.workerPath + '/tcp-socket-tls-worker.min.js';
//
// Public API
//
@ -54,7 +54,7 @@ ConnectionDoctor.prototype.configure = function(credentials) {
secure: this.credentials.imap.secure,
ignoreTLS: this.credentials.imap.ignoreTLS,
ca: this.credentials.imap.ca,
tlsWorkerPath: WORKER_PATH,
tlsWorkerPath: this._workerPath,
auth: {
user: this.credentials.username,
pass: this.credentials.password,
@ -66,7 +66,7 @@ ConnectionDoctor.prototype.configure = function(credentials) {
useSecureTransport: this.credentials.smtp.secure,
ignoreTLS: this.credentials.smtp.ignoreTLS,
ca: this.credentials.smtp.ca,
tlsWorkerPath: WORKER_PATH,
tlsWorkerPath: this._workerPath,
auth: {
user: this.credentials.username,
pass: this.credentials.password,
@ -133,7 +133,7 @@ ConnectionDoctor.prototype._checkOnline = function(callback) {
if (navigator.onLine) {
callback();
} else {
callback(createError(OFFLINE, strings.connDocOffline));
callback(createError(OFFLINE, this._appConfig.string.connDocOffline));
}
};
@ -150,18 +150,20 @@ ConnectionDoctor.prototype._checkReachable = function(options, callback) {
error, // remember the error message
timeout, // remember the timeout object
host = options.host + ':' + options.port,
hasTimedOut = false; // prevents multiple callbacks
hasTimedOut = false, // prevents multiple callbacks
cfg = this._appConfig.config,
str = this._appConfig.string;
timeout = setTimeout(function() {
hasTimedOut = true;
callback(createError(HOST_TIMEOUT, strings.connDocHostTimeout.replace('{0}', host).replace('{1}', cfg.connDocTimeout)));
callback(createError(HOST_TIMEOUT, str.connDocHostTimeout.replace('{0}', host).replace('{1}', cfg.connDocTimeout)));
}, cfg.connDocTimeout);
socket = TCPSocket.open(options.host, options.port, {
binaryType: 'arraybuffer',
useSecureTransport: options.secure,
ca: options.ca,
tlsWorkerPath: WORKER_PATH
tlsWorkerPath: this._workerPath
});
socket.ondata = function() {}; // we don't actually care about the data
@ -174,14 +176,14 @@ ConnectionDoctor.prototype._checkReachable = function(options, callback) {
socket.oncert = function() {
if (options.ca) {
// the certificate we already have is outdated
error = createError(TLS_WRONG_CERT, strings.connDocTlsWrongCert.replace('{0}', host));
error = createError(TLS_WRONG_CERT, str.connDocTlsWrongCert.replace('{0}', host));
}
};
} catch (e) {}
socket.onerror = function(e) {
if (!error) {
error = createError(HOST_UNREACHABLE, strings.connDocHostUnreachable.replace('{0}', host), e.data);
error = createError(HOST_UNREACHABLE, str.connDocHostUnreachable.replace('{0}', host), e.data);
}
};
@ -206,8 +208,8 @@ ConnectionDoctor.prototype._checkReachable = function(options, callback) {
ConnectionDoctor.prototype._checkImap = function(callback) {
var self = this,
loggedIn = false,
host = self.credentials.imap.host + ':' + self.credentials.imap.port;
host = self.credentials.imap.host + ':' + self.credentials.imap.port,
str = this._appConfig.string;
self._imap.onCert = function(pemEncodedCert) {
if (!self.credentials.imap.ca) {
@ -219,9 +221,9 @@ ConnectionDoctor.prototype._checkImap = function(callback) {
// the global onError handler, so we need to track if login was successful
self._imap.onError = function(error) {
if (!loggedIn) {
callback(createError(AUTH_REJECTED, strings.connDocAuthRejected.replace('{0}', host), error));
callback(createError(AUTH_REJECTED, str.connDocAuthRejected.replace('{0}', host), error));
} else {
callback(createError(GENERIC_ERROR, strings.connDocGenericError.replace('{0}', host).replace('{1}', error.message), error));
callback(createError(GENERIC_ERROR, str.connDocGenericError.replace('{0}', host).replace('{1}', error.message), error));
}
};
@ -230,12 +232,12 @@ ConnectionDoctor.prototype._checkImap = function(callback) {
self._imap.listWellKnownFolders(function(error, wellKnownFolders) {
if (error) {
return callback(createError(GENERIC_ERROR, strings.connDocGenericError.replace('{0}', host).replace('{1}', error.message), error));
return callback(createError(GENERIC_ERROR, str.connDocGenericError.replace('{0}', host).replace('{1}', error.message), error));
}
if (wellKnownFolders.Inbox.length === 0) {
// the client needs at least an inbox folder to work properly
return callback(createError(NO_INBOX, strings.connDocNoInbox.replace('{0}', host)));
return callback(createError(NO_INBOX, str.connDocNoInbox.replace('{0}', host)));
}
self._imap.logout(function() {
@ -254,7 +256,8 @@ ConnectionDoctor.prototype._checkImap = function(callback) {
ConnectionDoctor.prototype._checkSmtp = function(callback) {
var self = this,
host = self.credentials.smtp.host + ':' + self.credentials.smtp.port,
errored = false; // tracks if we need to invoke the callback at onclose or not
errored = false, // tracks if we need to invoke the callback at onclose or not
str = this._appConfig.string;
self._smtp.oncert = function(pemEncodedCert) {
if (!self.credentials.smtp.ca) {
@ -265,7 +268,7 @@ ConnectionDoctor.prototype._checkSmtp = function(callback) {
self._smtp.onerror = function(error) {
if (error) {
errored = true;
callback(createError(AUTH_REJECTED, strings.connDocAuthRejected.replace('{0}', host), error));
callback(createError(AUTH_REJECTED, str.connDocAuthRejected.replace('{0}', host), error));
}
};
@ -293,6 +296,4 @@ function createError(code, message, underlyingError) {
error.underlyingError = underlyingError;
return error;
}
module.exports = ConnectionDoctor;
}

68
src/js/util/dialog.js Normal file
View File

@ -0,0 +1,68 @@
'use strict';
var ngModule = angular.module('woUtil');
ngModule.service('dialog', Dialog);
module.exports = Dialog;
/**
* A central service to display messages to the user in a dialog
*/
function Dialog($q, axe) {
this._q = $q;
this._axe = axe;
// binds the methods to the instance of the dialog service so that we can e.g.
// pass dialog.error as a callback to asynchronous functions without having to
// do dialog.error.bind(dialog) every time
this.info = this.info.bind(this);
this.error = this.error.bind(this);
this.confirm = this.confirm.bind(this);
}
/**
* Show an information dialog
* @param {String} options.title The title of the displayed dialog
* @param {String} options.message The message to be displayed
* @return {Promise}
*/
Dialog.prototype.info = function(options) {
return this._handle(options, this.displayInfo, 'displayInfo');
};
/**
* Show an error dialog
* @param {String} options.title (optional) The title of the displayed dialog
* @param {String} options.message The message to be displayed
* @return {Promise}
*/
Dialog.prototype.error = function(options) {
// log the error
if (options) {
this._axe.error((options.errMsg || options.message) + (options.stack ? ('\n' + options.stack) : ''));
}
return this._handle(options, this.displayError, 'displayError');
};
/**
* Show an confirm dialog
* @param {String} options.title The title of the displayed dialog
* @param {String} options.message The message to be displayed
* @param {String} options.callback The callback that is called after the confirmation has been granted or denied
* @return {Promise}
*/
Dialog.prototype.confirm = function(options) {
return this._handle(options, this.displayConfirm, 'displayConfirm');
};
/**
* Helper function which returns a promise
*/
Dialog.prototype._handle = function(options, fn, errMsg) {
return this._q(function(resolve, reject) {
if (fn) {
return resolve(fn(options));
} else {
reject(new Error('Dialog service ' + errMsg + ' not set!'));
}
});
};

View File

@ -1,10 +1,20 @@
'use strict';
var ngModule = angular.module('woUtil');
ngModule.service('download', Download);
module.exports = Download;
var util = require('crypto-lib').util;
var dl = {};
/**
* A download helper to abstract platform specific behavior
*/
function Download() {}
dl.createDownload = function(options) {
/**
* Create download link and click on it.
*/
Download.prototype.createDownload = function(options) {
var contentType = options.contentType || 'application/octet-stream';
var filename = options.filename || 'file';
var content = options.content;
@ -53,6 +63,4 @@ dl.createDownload = function(options) {
}
window.open('data:' + contentType + ';base64,' + btoa(content), "_blank");
}
};
module.exports = dl;
};

73
src/js/util/dummy.js Normal file
View File

@ -0,0 +1,73 @@
'use strict';
var ngModule = angular.module('woUtil');
ngModule.service('dummy', Dummy);
module.exports = Dummy;
function Dummy() {}
Dummy.prototype.listMails = function() {
var uid = 1000000;
var Email = function(unread, attachments, answered) {
this.uid = uid--;
this.from = [{
name: 'Whiteout Support',
address: 'support@whiteout.io'
}]; // sender address
this.to = [{
address: 'max.musterman@gmail.com'
}, {
address: 'max.musterman@gmail.com'
}]; // list of receivers
this.cc = [{
address: 'john.doe@gmail.com'
}]; // list of receivers
this.attachments = attachments ? [{
"filename": "a.md",
"filesize": 123,
"mimeType": "text/x-markdown",
"part": "2",
"content": null
}, {
"filename": "b.md",
"filesize": 456,
"mimeType": "text/x-markdown",
"part": "3",
"content": null
}, {
"filename": "c.md",
"filesize": 789,
"mimeType": "text/x-markdown",
"part": "4",
"content": null
}] : [];
this.unread = unread;
this.answered = answered;
this.sentDate = new Date('Thu Sep 19 2013 20:41:23 GMT+0200 (CEST)');
this.subject = 'Getting started'; // Subject line
this.body = 'And a good day to you too sir. \n' +
'\n' +
'Thursday, Apr 24, 2014 3:33 PM safewithme.testuser@gmail.com wrote:\n' +
'> adsfadfasdfasdfasfdasdfasdfas\n' +
'\n' +
'http://example.com\n' +
'\n' +
'> Tuesday, Mar 25, 2014 4:19 PM gianniarcore@gmail.com wrote:\n' +
'>> from 0.7.0.1\n' +
'>>\n' +
'>> God speed!'; // plaintext body
//this.html = '<!DOCTYPE html><html><head></head><body><h1 style="border: 1px solid red; width: 500px;">Hello there' + Math.random() + '</h1></body></html>';
this.encrypted = true;
this.decrypted = true;
};
var dummies = [],
i = 100;
while (i--) {
// every second/third/fourth dummy mail with unread/attachments/answered
dummies.push(new Email((i % 2 === 0), (i % 3 === 0), (i % 5 === 0)));
}
return dummies;
};

View File

@ -1,33 +0,0 @@
'use strict';
var axe = require('axe-logger');
var er = {};
er.attachHandler = function(scope) {
scope.onError = function(options) {
if (!options) {
scope.$apply();
return;
}
axe.error((options.errMsg || options.message) + (options.stack ? ('\n' + options.stack) : ''));
scope.state.dialog = {
open: true,
title: options.title || 'Error',
message: options.errMsg || options.message,
faqLink: options.faqLink,
positiveBtnStr: options.positiveBtnStr || 'Ok',
negativeBtnStr: options.negativeBtnStr || 'Cancel',
showNegativeBtn: options.showNegativeBtn || false,
showBugReporter: (typeof options.showBugReporter !== 'undefined' ? options.showBugReporter : !options.title), // if title is set, presume it's not an error by default
callback: options.callback
};
// don't call apply for synchronous calls
if (!options.sync) {
scope.$apply();
}
};
};
module.exports = er;

12
src/js/util/index.js Normal file
View File

@ -0,0 +1,12 @@
'use strict';
angular.module('woUtil', []);
require('./axe');
require('./dummy');
require('./dialog');
require('./connection-doctor');
require('./update/update-handler');
require('./status-display');
require('./download');
require('./notification');

View File

@ -1,11 +1,15 @@
'use strict';
var cfg = require('../app-config').config;
var ngModule = angular.module('woUtil');
ngModule.service('notification', Notif);
module.exports = Notif;
var notif = {};
function Notif(appConfig) {
this._appConfig = appConfig;
if (window.Notification) {
notif.hasPermission = Notification.permission === "granted";
if (window.Notification) {
this.hasPermission = Notification.permission === "granted";
}
}
/**
@ -17,25 +21,27 @@ if (window.Notification) {
* @param {Function} options.onClick (optional) callback when the notification is clicked
* @returns {Notification} A notification instance
*/
notif.create = function(options) {
Notif.prototype.create = function(options) {
var self = this;
options.onClick = options.onClick || function() {};
if (!window.Notification) {
return;
}
if (!notif.hasPermission) {
if (!self.hasPermission) {
// don't wait until callback returns
Notification.requestPermission(function(permission) {
if (permission === "granted") {
notif.hasPermission = true;
self.hasPermission = true;
}
});
}
var notification = new Notification(options.title, {
body: options.message,
icon: cfg.iconPath
icon: self._appConfig.config.iconPath
});
notification.onclick = function() {
window.focus();
@ -51,8 +57,6 @@ notif.create = function(options) {
return notification;
};
notif.close = function(notification) {
Notif.prototype.close = function(notification) {
notification.close();
};
module.exports = notif;
};

View File

@ -0,0 +1,31 @@
'use strict';
var ngModule = angular.module('woUtil');
ngModule.service('statusDisplay', StatusDisplay);
module.exports = StatusDisplay;
/**
* A central service to display status updates to the user
*/
function StatusDisplay($rootScope, axe) {
this._rootScope = $rootScope;
this._axe = axe;
}
/**
* Update the status disply in the lower left of the screen
* @param {String} text The status message that is to be displayed to the user
* @param {Date} time The time of the last update
*/
StatusDisplay.prototype.update = function(text, time) {
this._axe.info('status display', text);
this._rootScope.$broadcast('status', text, time);
};
/**
* Update the searching status to show a spinner while searching
* @param {Boolean} state If the spinner should be displayed or not
*/
StatusDisplay.prototype.setSearching = function(state) {
this._rootScope.$broadcast('searching', state);
};

View File

@ -1,5 +1,9 @@
'use strict';
var ngModule = angular.module('woUtil');
ngModule.service('updateHandler', UpdateHandler);
module.exports = UpdateHandler;
var axe = require('axe-logger'),
cfg = require('../../app-config').config,
updateV1 = require('./update-v1'),
@ -11,12 +15,13 @@ var axe = require('axe-logger'),
/**
* Handles database migration
*/
var UpdateHandler = function(appConfigStorage, userStorage, auth) {
this._appConfigStorage = appConfigStorage;
this._userStorage = userStorage;
function UpdateHandler(appConfigStore, accountStore, auth, dialog) {
this._appConfigStorage = appConfigStore;
this._userStorage = accountStore;
this._updateScripts = [updateV1, updateV2, updateV3, updateV4, updateV5];
this._auth = auth;
};
this._dialog = dialog;
}
/**
* Executes all the necessary updates
@ -95,14 +100,16 @@ UpdateHandler.prototype._applyUpdate = function(options, callback) {
/**
* Check application version and update correspondingly
*/
UpdateHandler.prototype.checkForUpdate = function(dialog) {
UpdateHandler.prototype.checkForUpdate = function() {
var self = this;
// Chrome Packaged App
if (typeof window.chrome !== 'undefined' && chrome.runtime && chrome.runtime.onUpdateAvailable) {
// check for Chrome app update and restart
chrome.runtime.onUpdateAvailable.addListener(function(details) {
axe.debug('New Chrome App update... requesting reload.');
// Chrome downloaded a new app version
dialog({
self._dialog.confirm({
title: 'Update available',
message: 'A new version ' + details.version + ' of the app is available. Restart the app to update?',
positiveBtnStr: 'Restart',
@ -125,6 +132,4 @@ UpdateHandler.prototype.checkForUpdate = function(dialog) {
}
});
}
};
module.exports = UpdateHandler;
};

View File

@ -1,19 +1,19 @@
<div class="lightbox__body" ng-controller="DialogCtrl">
<header class="lightbox__header">
<h2>{{state.dialog.title}}</h2>
<h2>{{title}}</h2>
<button class="lightbox__close" wo-touch="confirm(false)" data-action="lightbox-close">
<svg><use xlink:href="#icon-close" /><title>Close</title></svg>
</button>
</header>
<div class="lightbox__content">
<p class="typo-paragraph">{{state.dialog.message}} <a ng-show="state.dialog.faqLink" href="{{state.dialog.faqLink}}" target="_blank">Learn more</a></p>
<p class="typo-paragraph">{{message}} <a ng-show="faqLink" href="{{faqLink}}" target="_blank">Learn more</a></p>
</div>
<footer class="lightbox__controls">
<button wo-touch="confirm(false)" class="btn btn--secondary" ng-show="state.dialog.showNegativeBtn">{{state.dialog.negativeBtnStr}}</button>
<button wo-touch="confirm(false)" class="btn btn--secondary" ng-show="showNegativeBtn">{{negativeBtnStr}}</button>
<!-- only show bug report button if we can actually report a bug, i.e. the writer is attached to the scope -->
<button wo-touch="confirm(true)" class="btn" ng-show="!state.writer || !state.dialog.showBugReporter">{{state.dialog.positiveBtnStr}}</button>
<button wo-touch="confirm(true); state.writer.reportBug()" class="btn" ng-show="state.writer && state.dialog.showBugReporter">Report bug</button>
<button wo-touch="confirm(true)" class="btn" ng-show="!state.writer || !showBugReporter">{{positiveBtnStr}}</button>
<button wo-touch="confirm(true); state.writer.reportBug()" class="btn" ng-show="state.writer && showBugReporter">Report bug</button>
</footer>
</div>
</div>

View File

@ -12,6 +12,7 @@
<svg><use xlink:href="#icon-write" /><title>New mail</title></svg>
</button>
</header>
<div class="mail-list__search">
<div class="search">
<svg><use xlink:href="#icon-search" /><title>Search</title></svg>
@ -71,13 +72,5 @@
</ul>
</div>
<footer>
<span class="spinner" ng-show="account.loggingIn || account.busy || state.mailList.searching"></span>
<span class="text" ng-switch="account.online">
<span ng-switch-when="false">
<svg><use xlink:href="#icon-offline" /></svg>
</span>
{{state.mailList.lastUpdateLbl}} {{state.mailList.lastUpdate | date:'shortTime'}}
</span>
</footer>
</div>
<footer ng-include="'tpl/status-display.html'"></footer>
</div>

View File

@ -69,13 +69,5 @@
</li>
</ul><!--/nav__secondary-->
<footer>
<span class="spinner" ng-show="account.loggingIn || account.busy || state.mailList.searching"></span>
<span class="text" ng-switch="account.online">
<span ng-switch-when="false">
<svg><use xlink:href="#icon-offline" /></svg>
</span>
{{state.mailList.lastUpdateLbl}} {{state.mailList.lastUpdate | date:'shortTime'}}
</span>
</footer>
<footer ng-include="'tpl/status-display.html'"></footer>
</nav>

View File

@ -0,0 +1,9 @@
<div ng-controller="StatusDisplayCtrl">
<span class="spinner" ng-show="account.loggingIn || account.busy || searching"></span>
<span class="text" ng-switch="account.online">
<span ng-switch-when="false">
<svg><use xlink:href="#icon-offline" /></svg>
</span>
{{text}} {{time | date:'shortTime'}}
</span>
</div>

File diff suppressed because one or more lines are too long

View File

@ -26,7 +26,9 @@
if (window.mochaPhantomJS) {
mochaPhantomJS.run();
} else {
mocha.run();
setTimeout(function() {
mocha.run();
}, 1000)
}
</script>
</body>

View File

@ -46,8 +46,21 @@ if (!Function.prototype.bind) {
}
})();
//
// Test setup
//
chai.config.includeStack = true;
// set worker path for tests
require('../src/js/app-config').config.workerPath = '../lib';
var axe = require('axe-logger');
axe.removeAppender(axe.defaultAppender);
axe.removeAppender(axe.defaultAppender);
// include angular modules
require('../src/js/app-config');
require('../src/js/util');
require('../src/js/crypto');
require('../src/js/service');
require('../src/js/email');

View File

@ -1,282 +0,0 @@
'use strict';
var controller = require('../../src/js/app-controller'),
EmailDAO = require('../../src/js/dao/email-dao'),
OutboxBO = require('../../src/js/bo/outbox'),
DeviceStorageDAO = require('../../src/js/dao/devicestorage-dao'),
UpdateHandler = require('../../src/js/util/update/update-handler'),
KeychainDAO = require('../../src/js/dao/keychain-dao'),
config = require('../../src/js/app-config').config,
Auth = require('../../src/js/bo/auth');
describe('App Controller unit tests', function() {
var emailDaoStub, outboxStub, updateHandlerStub, appConfigStoreStub, devicestorageStub, isOnlineStub, authStub, keychainStub;
beforeEach(function() {
controller._emailDao = emailDaoStub = sinon.createStubInstance(EmailDAO);
controller._outboxBo = outboxStub = sinon.createStubInstance(OutboxBO);
controller._appConfigStore = appConfigStoreStub = sinon.createStubInstance(DeviceStorageDAO);
controller._userStorage = devicestorageStub = sinon.createStubInstance(DeviceStorageDAO);
controller._updateHandler = updateHandlerStub = sinon.createStubInstance(UpdateHandler);
controller._auth = authStub = sinon.createStubInstance(Auth);
controller._keychain = keychainStub = sinon.createStubInstance(KeychainDAO);
isOnlineStub = sinon.stub(controller, 'isOnline');
});
afterEach(function() {
isOnlineStub.restore();
});
describe('buildModules', function() {
it('should work', function() {
controller.buildModules({
onError: function() {}
});
expect(controller._appConfigStore).to.exist;
expect(controller._auth).to.exist;
expect(controller._userStorage).to.exist;
expect(controller._invitationDao).to.exist;
expect(controller._keychain).to.exist;
expect(controller._pgp).to.exist;
expect(controller._pgpbuilder).to.exist;
expect(controller._emailDao).to.exist;
expect(controller._outboxBo).to.exist;
expect(controller._updateHandler).to.exist;
});
});
describe('start', function() {
it('should not explode', function(done) {
controller.start({
onError: function() {}
}, function(err) {
expect(err).to.not.exist;
done();
});
});
});
describe('onDisconnect', function() {
it('should work', function() {
controller.onDisconnect();
expect(emailDaoStub.onDisconnect.calledOnce).to.be.true;
});
});
describe('logout', function() {
it('should work', function(done) {
authStub.logout.yields();
emailDaoStub.onDisconnect.yields(new Error());
controller.onError = function(err) {
expect(err).to.exist;
expect(authStub.logout.calledOnce).to.be.true;
expect(emailDaoStub.onDisconnect.calledOnce).to.be.true;
done();
};
controller.logout();
});
});
describe('onConnect', function() {
beforeEach(function() {
controller._emailDao._account = {};
});
it('should not connect if offline', function(done) {
isOnlineStub.returns(false);
controller.onConnect(function(err) {
expect(err).to.not.exist;
done();
});
});
it('should not connect if account is not initialized', function(done) {
controller._emailDao._account = null;
controller.onConnect(function(err) {
expect(err).to.not.exist;
done();
});
});
it('should fail due to error in auth.getCredentials', function(done) {
isOnlineStub.returns(true);
authStub.getCredentials.yields(new Error());
controller.onConnect(function(err) {
expect(err).to.exist;
expect(authStub.getCredentials.calledOnce).to.be.true;
done();
});
});
it('should work', function(done) {
isOnlineStub.returns(true);
authStub.getCredentials.yields(null, {
emailAddress: 'asdf@example.com',
oauthToken: 'token',
sslCert: 'cert',
imap: {},
smtp: {}
});
emailDaoStub.onConnect.yields();
controller.onConnect(function(err) {
expect(err).to.not.exist;
expect(authStub.getCredentials.calledOnce).to.be.true;
expect(emailDaoStub.onConnect.calledOnce).to.be.true;
done();
});
});
});
describe('init', function() {
var onConnectStub, emailAddress, keysWithPubKey;
beforeEach(function() {
emailAddress = 'alice@bob.com';
keysWithPubKey = {
publicKey: {}
};
// onConnect
onConnectStub = sinon.stub(controller, 'onConnect');
});
afterEach(function() {
onConnectStub.restore();
});
it('should fail due to malformed email address', function(done) {
controller.init({
emailAddress: 'ishallfail'
}, function(err, keypair) {
expect(err).to.exist;
expect(keypair).to.not.exist;
done();
});
});
it('should fail due to error in storage initialization', function(done) {
devicestorageStub.init.withArgs(emailAddress).yields(new Error());
controller.init({
emailAddress: emailAddress
}, function(err, keypair) {
expect(err).to.exist;
expect(keypair).to.not.exist;
expect(devicestorageStub.init.calledOnce).to.be.true;
expect(updateHandlerStub.update.calledOnce).to.be.false;
done();
});
});
it('should fail due to error in update handler', function(done) {
devicestorageStub.init.yields();
updateHandlerStub.update.yields(new Error());
controller.init({
emailAddress: emailAddress
}, function(err, keypair) {
expect(err).to.exist;
expect(keypair).to.not.exist;
expect(updateHandlerStub.update.calledOnce).to.be.true;
expect(devicestorageStub.init.calledOnce).to.be.true;
done();
});
});
it('should fail due to error in getUserKeyPair', function(done) {
devicestorageStub.init.yields();
updateHandlerStub.update.yields();
keychainStub.getUserKeyPair.yields(new Error());
controller.init({
emailAddress: emailAddress
}, function(err, keypair) {
expect(err).to.exist;
expect(keypair).to.not.exist;
expect(updateHandlerStub.update.calledOnce).to.be.true;
expect(devicestorageStub.init.calledOnce).to.be.true;
expect(keychainStub.getUserKeyPair.calledOnce).to.be.true;
done();
});
});
it('should fail due to error in refreshKeyForUserId', function(done) {
devicestorageStub.init.yields();
updateHandlerStub.update.yields();
keychainStub.getUserKeyPair.yields(null, keysWithPubKey);
keychainStub.refreshKeyForUserId.yields(new Error());
controller.init({
emailAddress: emailAddress
}, function(err, keypair) {
expect(err).to.exist;
expect(keypair).to.not.exist;
expect(updateHandlerStub.update.calledOnce).to.be.true;
expect(devicestorageStub.init.calledOnce).to.be.true;
expect(keychainStub.getUserKeyPair.calledOnce).to.be.true;
expect(keychainStub.refreshKeyForUserId.calledOnce).to.be.true;
done();
});
});
it('should fail due to error in emailDao.init', function(done) {
devicestorageStub.init.yields();
updateHandlerStub.update.yields();
keychainStub.getUserKeyPair.yields(null, keysWithPubKey);
keychainStub.refreshKeyForUserId.yields();
emailDaoStub.init.yields(new Error());
controller.init({
emailAddress: emailAddress
}, function(err, keypair) {
expect(err).to.exist;
expect(keypair).to.not.exist;
expect(updateHandlerStub.update.calledOnce).to.be.true;
expect(devicestorageStub.init.calledOnce).to.be.true;
expect(keychainStub.getUserKeyPair.calledOnce).to.be.true;
expect(keychainStub.refreshKeyForUserId.calledOnce).to.be.true;
expect(emailDaoStub.init.calledOnce).to.be.true;
done();
});
});
it('should work and not return a keypair', function(done) {
devicestorageStub.init.withArgs(emailAddress).yields();
updateHandlerStub.update.yields();
keychainStub.getUserKeyPair.withArgs(emailAddress).yields(null, keysWithPubKey);
keychainStub.refreshKeyForUserId.withArgs({
userId: emailAddress,
overridePermission: true
}).yields();
emailDaoStub.init.withArgs({
account: {
realname: undefined,
emailAddress: emailAddress,
asymKeySize: config.asymKeySize
}
}).yields();
controller.init({
emailAddress: emailAddress
}, function(err, keypair) {
expect(err).to.not.exist;
expect(keypair.publicKey).to.not.exist;
expect(updateHandlerStub.update.calledOnce).to.be.true;
expect(devicestorageStub.init.calledOnce).to.be.true;
expect(keychainStub.getUserKeyPair.calledOnce).to.be.true;
expect(keychainStub.refreshKeyForUserId.calledOnce).to.be.true;
expect(emailDaoStub.init.calledOnce).to.be.true;
done();
});
});
});
});

View File

@ -1,50 +1,53 @@
'use strict';
var mocks = angular.mock,
AccountCtrl = require('../../src/js/controller/account'),
PGP = require('../../src/js/crypto/pgp'),
dl = require('../../src/js/util/download'),
appController = require('../../src/js/app-controller'),
KeychainDAO = require('../../src/js/dao/keychain-dao');
var AccountCtrl = require('../../../../src/js/controller/app/account'),
PGP = require('../../../../src/js/crypto/pgp'),
Download = require('../../../../src/js/util/download'),
Keychain = require('../../../../src/js/service/keychain'),
Auth = require('../../../../src/js/service/auth'),
Dialog = require('../../../../src/js/util/dialog');
describe('Account Controller unit test', function() {
var scope, accountCtrl,
dummyFingerprint, expectedFingerprint,
dummyKeyId, expectedKeyId,
emailAddress, keySize, pgpMock, keychainMock;
emailAddress, keySize, pgpStub, keychainStub, authStub, dialogStub, downloadStub;
beforeEach(function() {
appController._pgp = pgpMock = sinon.createStubInstance(PGP);
appController._keychain = keychainMock = sinon.createStubInstance(KeychainDAO);
pgpStub = sinon.createStubInstance(PGP);
authStub = sinon.createStubInstance(Auth);
keychainStub = sinon.createStubInstance(Keychain);
dialogStub = sinon.createStubInstance(Dialog);
downloadStub = sinon.createStubInstance(Download);
dummyFingerprint = '3A2D39B4E1404190B8B949DE7D7E99036E712926';
expectedFingerprint = '3A2D 39B4 E140 4190 B8B9 49DE 7D7E 9903 6E71 2926';
dummyKeyId = '9FEB47936E712926';
expectedKeyId = '6E712926';
pgpMock.getFingerprint.returns(dummyFingerprint);
pgpMock.getKeyId.returns(dummyKeyId);
pgpStub.getFingerprint.returns(dummyFingerprint);
pgpStub.getKeyId.returns(dummyKeyId);
emailAddress = 'fred@foo.com';
keySize = 1234;
appController._emailDao = {
_account: {
emailAddress: emailAddress,
asymKeySize: keySize
}
};
pgpMock.getKeyParams.returns({
authStub.emailAddress = emailAddress;
pgpStub.getKeyParams.returns({
_id: dummyKeyId,
fingerprint: dummyFingerprint,
userId: emailAddress,
bitSize: keySize
});
angular.module('accounttest', []);
mocks.module('accounttest');
mocks.inject(function($rootScope, $controller) {
angular.module('accounttest', ['woServices']);
angular.mock.module('accounttest');
angular.mock.inject(function($rootScope, $controller) {
scope = $rootScope.$new();
scope.state = {};
accountCtrl = $controller(AccountCtrl, {
$scope: scope
$scope: scope,
auth: authStub,
keychain: keychainStub,
pgp: pgpStub,
download: downloadStub,
dialog: dialogStub
});
});
});
@ -61,8 +64,7 @@ describe('Account Controller unit test', function() {
});
describe('export to key file', function() {
it('should work', function() {
var createDownloadMock = sinon.stub(dl, 'createDownload');
keychainMock.getUserKeyPair.withArgs(emailAddress).yields(null, {
keychainStub.getUserKeyPair.withArgs(emailAddress).yields(null, {
publicKey: {
_id: dummyKeyId,
publicKey: 'a'
@ -71,27 +73,23 @@ describe('Account Controller unit test', function() {
encryptedKey: 'b'
}
});
createDownloadMock.withArgs(sinon.match(function(arg) {
downloadStub.createDownload.withArgs(sinon.match(function(arg) {
return arg.content === 'a\r\nb' && arg.filename === 'whiteout_mail_' + emailAddress + '_' + expectedKeyId + '.asc' && arg.contentType === 'text/plain';
})).returns();
scope.exportKeyFile();
expect(scope.state.lightbox).to.equal(undefined);
expect(keychainMock.getUserKeyPair.calledOnce).to.be.true;
expect(dl.createDownload.calledOnce).to.be.true;
dl.createDownload.restore();
expect(keychainStub.getUserKeyPair.calledOnce).to.be.true;
expect(downloadStub.createDownload.calledOnce).to.be.true;
});
it('should not work when key export failed', function(done) {
keychainMock.getUserKeyPair.yields(new Error('Boom!'));
scope.onError = function(err) {
expect(err.message).to.equal('Boom!');
expect(keychainMock.getUserKeyPair.calledOnce).to.be.true;
done();
};
it('should not work when key export failed', function() {
keychainStub.getUserKeyPair.yields(new Error());
scope.exportKeyFile();
expect(dialogStub.error.calledOnce).to.be.true;
});
});
});

View File

@ -1,21 +1,21 @@
'use strict';
var mocks = angular.mock,
EmailDAO = require('../../src/js/dao/email-dao'),
appController = require('../../src/js/app-controller'),
ActionBarCtrl = require('../../src/js/controller/action-bar');
var Email = require('../../../../src/js/email/email'),
Dialog = require('../../../../src/js/util/dialog'),
StatusDisplay = require('../../../../src/js/util/status-display'),
ActionBarCtrl = require('../../../../src/js/controller/app/action-bar');
describe('Action Bar Controller unit test', function() {
var scope, actionBarCtrl, emailDaoMock, origEmailDao;
var scope, actionBarCtrl, emailMock, dialogMock, statusDisplayMock;
beforeEach(function() {
origEmailDao = appController._emailDao;
emailDaoMock = sinon.createStubInstance(EmailDAO);
appController._emailDao = emailDaoMock;
emailMock = sinon.createStubInstance(Email);
dialogMock = sinon.createStubInstance(Dialog);
statusDisplayMock = sinon.createStubInstance(StatusDisplay);
angular.module('actionbartest', []);
mocks.module('actionbartest');
mocks.inject(function($rootScope, $controller) {
angular.mock.module('actionbartest');
angular.mock.inject(function($rootScope, $controller) {
scope = $rootScope.$new();
scope.state = {
mailList: {
@ -40,15 +40,15 @@ describe('Action Bar Controller unit test', function() {
};
actionBarCtrl = $controller(ActionBarCtrl, {
$scope: scope
$scope: scope,
email: emailMock,
dialog: dialogMock,
statusDisplay: statusDisplayMock
});
});
});
afterEach(function() {
// restore the module
appController._emailDao = origEmailDao;
});
afterEach(function() {});
describe('deleteMessage', function() {
it('should not delete without a selected mail', function() {
@ -56,11 +56,11 @@ describe('Action Bar Controller unit test', function() {
});
it('should delete the selected mail', function() {
emailDaoMock.deleteMessage.yields();
emailMock.deleteMessage.yields();
scope.deleteMessage({});
expect(emailDaoMock.deleteMessage.calledOnce).to.be.true;
expect(emailMock.deleteMessage.calledOnce).to.be.true;
expect(scope.state.read.open).to.be.false;
});
});
@ -88,11 +88,11 @@ describe('Action Bar Controller unit test', function() {
});
it('should move the selected mail', function() {
emailDaoMock.moveMessage.yields();
emailMock.moveMessage.yields();
scope.moveMessage({}, {});
expect(emailDaoMock.moveMessage.calledOnce).to.be.true;
expect(emailMock.moveMessage.calledOnce).to.be.true;
expect(scope.state.read.open).to.be.false;
});
});
@ -120,11 +120,11 @@ describe('Action Bar Controller unit test', function() {
});
it('should move the selected mail', function() {
emailDaoMock.setFlags.yields();
emailMock.setFlags.yields();
scope.markMessage({}, true);
expect(emailDaoMock.setFlags.calledOnce).to.be.true;
expect(emailMock.setFlags.calledOnce).to.be.true;
expect(scope.state.read.open).to.be.false;
});
});

View File

@ -1,38 +1,33 @@
'use strict';
var mocks = angular.mock,
ContactsCtrl = require('../../src/js/controller/contacts'),
appController = require('../../src/js/app-controller'),
KeychainDAO = require('../../src/js/dao/keychain-dao'),
PGP = require('../../src/js/crypto/pgp');
var ContactsCtrl = require('../../../../src/js/controller/app/contacts'),
Keychain = require('../../../../src/js/service/keychain'),
PGP = require('../../../../src/js/crypto/pgp'),
Dialog = require('../../../../src/js/util/dialog');
describe('Contacts Controller unit test', function() {
var scope, contactsCtrl,
origKeychain, keychainMock,
origPgp, pgpMock;
var scope, contactsCtrl, keychainStub, pgpStub, dialogStub;
beforeEach(function() {
origPgp = appController._pgp;
appController._pgp = pgpMock = sinon.createStubInstance(PGP);
origKeychain = appController._keychain;
appController._keychain = keychainMock = sinon.createStubInstance(KeychainDAO);
pgpStub = sinon.createStubInstance(PGP);
keychainStub = sinon.createStubInstance(Keychain);
dialogStub = sinon.createStubInstance(Dialog);
angular.module('contactstest', []);
mocks.module('contactstest');
mocks.inject(function($rootScope, $controller) {
angular.module('contactstest', ['woServices']);
angular.mock.module('contactstest');
angular.mock.inject(function($rootScope, $controller) {
scope = $rootScope.$new();
scope.state = {};
contactsCtrl = $controller(ContactsCtrl, {
$scope: scope
$scope: scope,
keychain: keychainStub,
pgp: pgpStub,
dialog: dialogStub
});
});
});
afterEach(function() {
// restore the module
appController._pgp = origPgp;
appController._keychain = origKeychain;
});
afterEach(function() {});
describe('scope variables', function() {
it('should be set correctly', function() {
@ -41,34 +36,28 @@ describe('Contacts Controller unit test', function() {
});
describe('listKeys', function() {
it('should fail due to error in keychain.listLocalPublicKeys', function(done) {
keychainMock.listLocalPublicKeys.yields(42);
scope.onError = function(err) {
expect(err).to.equal(42);
done();
};
it('should fail due to error in keychain.listLocalPublicKeys', function() {
keychainStub.listLocalPublicKeys.yields(42);
scope.listKeys();
expect(dialogStub.error.calledOnce).to.be.true;
});
it('should work', function(done) {
keychainMock.listLocalPublicKeys.yields(null, [{
it('should work', function() {
keychainStub.listLocalPublicKeys.yields(null, [{
_id: '12345'
}]);
pgpMock.getKeyParams.returns({
pgpStub.getKeyParams.returns({
fingerprint: 'asdf'
});
scope.$apply = function() {
expect(scope.keys.length).to.equal(1);
expect(scope.keys[0]._id).to.equal('12345');
expect(scope.keys[0].fingerprint).to.equal('asdf');
done();
};
expect(scope.keys).to.not.exist;
scope.listKeys();
expect(scope.keys.length).to.equal(1);
expect(scope.keys[0]._id).to.equal('12345');
expect(scope.keys[0].fingerprint).to.equal('asdf');
});
});
@ -88,13 +77,13 @@ describe('Contacts Controller unit test', function() {
it('should work', function(done) {
var keyArmored = '-----BEGIN PGP PUBLIC KEY BLOCK-----';
pgpMock.getKeyParams.returns({
pgpStub.getKeyParams.returns({
_id: '12345',
userId: 'max@example.com',
userIds: []
});
keychainMock.saveLocalPublicKey.withArgs({
keychainStub.saveLocalPublicKey.withArgs({
_id: '12345',
userId: 'max@example.com',
userIds: [],
@ -109,46 +98,36 @@ describe('Contacts Controller unit test', function() {
scope.importKey(keyArmored);
});
it('should fail due to invalid armored key', function(done) {
it('should fail due to invalid armored key', function() {
var keyArmored = '-----BEGIN PGP PRIVATE KEY BLOCK-----';
scope.onError = function(err) {
expect(err).to.exist;
done();
};
scope.importKey(keyArmored);
expect(dialogStub.error.calledOnce).to.be.true;
});
it('should fail due to error in pgp.getKeyParams', function(done) {
it('should fail due to error in pgp.getKeyParams', function() {
var keyArmored = '-----BEGIN PGP PUBLIC KEY BLOCK-----';
pgpMock.getKeyParams.throws(new Error('WAT'));
scope.onError = function(err) {
expect(err).to.exist;
done();
};
pgpStub.getKeyParams.throws(new Error('WAT'));
scope.importKey(keyArmored);
expect(dialogStub.error.calledOnce).to.be.true;
});
it('should fail due to error in keychain.saveLocalPublicKey', function(done) {
it('should fail due to error in keychain.saveLocalPublicKey', function() {
var keyArmored = '-----BEGIN PGP PUBLIC KEY BLOCK-----';
pgpMock.getKeyParams.returns({
pgpStub.getKeyParams.returns({
_id: '12345',
userId: 'max@example.com'
});
keychainMock.saveLocalPublicKey.yields(42);
scope.onError = function(err) {
expect(err).to.equal(42);
done();
};
keychainStub.saveLocalPublicKey.yields(42);
scope.importKey(keyArmored);
expect(dialogStub.error.calledOnce).to.be.true;
});
});
@ -158,7 +137,7 @@ describe('Contacts Controller unit test', function() {
_id: '12345'
};
keychainMock.removeLocalPublicKey.withArgs('12345').yields();
keychainStub.removeLocalPublicKey.withArgs('12345').yields();
scope.listKeys = function() {
done();
@ -167,19 +146,16 @@ describe('Contacts Controller unit test', function() {
scope.removeKey(key);
});
it('should fail due to error in keychain.removeLocalPublicKey', function(done) {
it('should fail due to error in keychain.removeLocalPublicKey', function() {
var key = {
_id: '12345'
};
keychainMock.removeLocalPublicKey.withArgs('12345').yields(42);
scope.onError = function(err) {
expect(err).to.equal(42);
done();
};
keychainStub.removeLocalPublicKey.withArgs('12345').yields(42);
scope.removeKey(key);
expect(dialogStub.error.calledOnce).to.be.true;
});
});
});

View File

@ -1,21 +1,22 @@
'use strict';
var mocks = angular.mock,
DialogCtrl = require('../../src/js/controller/dialog');
var DialogCtrl = require('../../../../src/js/controller/app/dialog');
describe('Dialog Controller unit test', function() {
var scope, dialogCtrl;
var scope, dialogCtrl, dialogService;
beforeEach(function() {
angular.module('dialogtest', []);
mocks.module('dialogtest');
mocks.inject(function($rootScope, $controller) {
angular.module('dialogtest', ['woUtil']);
angular.mock.module('dialogtest');
angular.mock.inject(function($rootScope, $controller, dialog) {
scope = $rootScope.$new();
scope.state = {
dialog: {}
};
dialogService = dialog;
dialogCtrl = $controller(DialogCtrl, {
$scope: scope
$scope: scope,
dialog: dialog
});
});
});
@ -24,7 +25,7 @@ describe('Dialog Controller unit test', function() {
describe('confirm', function() {
it('should work', function(done) {
scope.state.dialog.callback = function(confirmed) {
scope.callback = function(confirmed) {
expect(confirmed).to.be.true;
expect(scope.state.dialog.open).to.be.false;
done();
@ -35,7 +36,7 @@ describe('Dialog Controller unit test', function() {
describe('cancel', function() {
it('should work', function(done) {
scope.state.dialog.callback = function(confirmed) {
scope.callback = function(confirmed) {
expect(confirmed).to.be.false;
expect(scope.state.dialog.open).to.be.false;
done();

View File

@ -1,17 +1,14 @@
'use strict';
var mocks = angular.mock,
MailListCtrl = require('../../src/js/controller/mail-list'),
EmailDAO = require('../../src/js/dao/email-dao'),
DeviceStorageDAO = require('../../src/js/dao/devicestorage-dao'),
KeychainDAO = require('../../src/js/dao/keychain-dao'),
appController = require('../../src/js/app-controller'),
notification = require('../../src/js/util/notification');
chai.config.includeStack = true;
var MailListCtrl = require('../../../../src/js/controller/app/mail-list'),
EmailDAO = require('../../../../src/js/email/email'),
KeychainDAO = require('../../../../src/js/service/keychain'),
StatusDisplay = require('../../../../src/js/util/status-display'),
Dialog = require('../../../../src/js/util/dialog'),
Search = require('../../../../src/js/email/search');
describe('Mail List controller unit test', function() {
var scope, ctrl, origEmailDao, emailDaoMock, keychainMock, deviceStorageMock,
var scope, ctrl, statusDisplayMock, notificationMock, emailMock, keychainMock, dialogMock, searchMock,
emailAddress, emails,
hasChrome, hasSocket, hasRuntime, hasIdentity;
@ -41,28 +38,23 @@ describe('Mail List controller unit test', function() {
}, {
unread: true
}];
appController._outboxBo = {
pendingEmails: emails
};
origEmailDao = appController._emailDao;
emailDaoMock = sinon.createStubInstance(EmailDAO);
appController._emailDao = emailDaoMock;
emailAddress = 'fred@foo.com';
emailDaoMock._account = {
emailAddress: emailAddress,
notificationMock = {
create: function() {},
close: function() {}
};
statusDisplayMock = sinon.createStubInstance(StatusDisplay);
emailMock = sinon.createStubInstance(EmailDAO);
keychainMock = sinon.createStubInstance(KeychainDAO);
appController._keychain = keychainMock;
dialogMock = sinon.createStubInstance(Dialog);
searchMock = sinon.createStubInstance(Search);
deviceStorageMock = sinon.createStubInstance(DeviceStorageDAO);
emailDaoMock._devicestorage = deviceStorageMock;
angular.module('maillisttest', []);
mocks.module('maillisttest');
mocks.inject(function($rootScope, $controller) {
angular.module('maillisttest', ['woEmail', 'woServices', 'woUtil']);
angular.mock.module('maillisttest');
angular.mock.inject(function($rootScope, $controller) {
scope = $rootScope.$new();
scope.state = {
read: {
@ -73,7 +65,13 @@ describe('Mail List controller unit test', function() {
scope.loadVisibleBodies = function() {};
ctrl = $controller(MailListCtrl, {
$scope: scope,
$routeParams: {}
$routeParams: {},
statusDisplay: statusDisplayMock,
notification: notificationMock,
email: emailMock,
keychain: keychainMock,
dialog: dialogMock,
search: searchMock
});
});
});
@ -91,9 +89,6 @@ describe('Mail List controller unit test', function() {
if (!hasIdentity) {
delete window.chrome.identity;
}
// restore the module
appController._emailDao = origEmailDao;
});
describe('displayMore', function() {
@ -137,104 +132,21 @@ describe('Mail List controller unit test', function() {
it('should show initial message on empty', function() {
scope.displaySearchResults();
expect(scope.state.mailList.searching).to.be.false;
expect(scope.state.mailList.lastUpdateLbl).to.equal('Online');
expect(statusDisplayMock.setSearching.withArgs(false).calledOnce).to.be.true;
expect(statusDisplayMock.update.withArgs('Online').calledOnce).to.be.true;
expect(scope.displayMessages.length).to.equal(2);
});
it('should show initial message on empty', function() {
var searchStub = sinon.stub(scope, 'search');
searchStub.returns(['a']);
searchMock.filter.returns(['a']);
scope.displaySearchResults('query');
expect(scope.state.mailList.searching).to.be.true;
expect(scope.state.mailList.lastUpdateLbl).to.equal('Searching ...');
expect(statusDisplayMock.setSearching.withArgs(true).calledOnce).to.be.true;
expect(statusDisplayMock.update.withArgs('Searching ...').calledOnce).to.be.true;
clock.tick(500);
expect(scope.displayMessages).to.deep.equal(['a']);
expect(scope.state.mailList.searching).to.be.false;
expect(scope.state.mailList.lastUpdateLbl).to.equal('Matches in this folder');
});
});
describe('search', function() {
var message1 = {
to: [{
name: 'name1',
address: 'address1'
}],
subject: 'subject1',
body: 'body1',
html: 'html1'
},
message2 = {
to: [{
name: 'name2',
address: 'address2'
}],
subject: 'subject2',
body: 'body2',
html: 'html2'
},
message3 = {
to: [{
name: 'name3',
address: 'address3'
}],
subject: 'subject3',
body: 'body1',
html: 'html1',
encrypted: true
},
message4 = {
to: [{
name: 'name4',
address: 'address4'
}],
subject: 'subject4',
body: 'body1',
html: 'html1',
encrypted: true,
decrypted: true
},
testMessages = [message1, message2, message3, message4];
it('return same messages array on empty query string', function() {
var result = scope.search(testMessages, '');
expect(result).to.equal(testMessages);
});
it('return message1 on matching subject', function() {
var result = scope.search(testMessages, 'subject1');
expect(result.length).to.equal(1);
expect(result[0]).to.equal(message1);
});
it('return message1 on matching name', function() {
var result = scope.search(testMessages, 'name1');
expect(result.length).to.equal(1);
expect(result[0]).to.equal(message1);
});
it('return message1 on matching address', function() {
var result = scope.search(testMessages, 'address1');
expect(result.length).to.equal(1);
expect(result[0]).to.equal(message1);
});
it('return plaintext and decrypted messages on matching body', function() {
var result = scope.search(testMessages, 'body1');
expect(result.length).to.equal(2);
expect(result[0]).to.equal(message1);
expect(result[1]).to.equal(message4);
});
it('return plaintext and decrypted messages on matching html', function() {
var result = scope.search(testMessages, 'html1');
expect(result.length).to.equal(2);
expect(result[0]).to.equal(message1);
expect(result[1]).to.equal(message4);
expect(statusDisplayMock.setSearching.withArgs(false).calledOnce).to.be.true;
expect(statusDisplayMock.update.withArgs('Matches in this folder').calledOnce).to.be.true;
});
});
@ -251,7 +163,7 @@ describe('Mail List controller unit test', function() {
});
afterEach(function() {
notification.create.restore();
notificationMock.create.restore();
});
it('should succeed for single mail', function(done) {
@ -264,7 +176,7 @@ describe('Mail List controller unit test', function() {
unread: true
};
sinon.stub(notification, 'create', function(opts) {
sinon.stub(notificationMock, 'create', function(opts) {
expect(opts.title).to.equal(mail.from[0].address);
expect(opts.message).to.equal(mail.subject);
@ -280,7 +192,7 @@ describe('Mail List controller unit test', function() {
}
};
emailDaoMock.onIncomingMessage([mail]);
emailMock.onIncomingMessage([mail]);
});
it('should succeed for multiple mails', function(done) {
@ -307,7 +219,7 @@ describe('Mail List controller unit test', function() {
unread: false
}];
sinon.stub(notification, 'create', function(opts) {
sinon.stub(notificationMock, 'create', function(opts) {
expect(opts.title).to.equal('2 new messages');
expect(opts.message).to.equal(mails[0].subject + '\n' + mails[1].subject);
@ -323,7 +235,7 @@ describe('Mail List controller unit test', function() {
}
};
emailDaoMock.onIncomingMessage(mails);
emailMock.onIncomingMessage(mails);
});
});
@ -336,14 +248,14 @@ describe('Mail List controller unit test', function() {
};
scope.getBody();
expect(emailDaoMock.getBody.calledOnce).to.be.true;
expect(emailMock.getBody.calledOnce).to.be.true;
});
});
describe('select', function() {
it('should decrypt, focus mark an unread mail as read', function() {
scope.pendingNotifications = ['asd'];
sinon.stub(notification, 'close');
sinon.stub(notificationMock, 'close');
var mail = {
from: [{
@ -372,13 +284,13 @@ describe('Mail List controller unit test', function() {
scope.select(mail);
expect(emailDaoMock.decryptBody.calledOnce).to.be.true;
expect(emailMock.decryptBody.calledOnce).to.be.true;
expect(keychainMock.refreshKeyForUserId.calledOnce).to.be.true;
expect(scope.state.mailList.selected).to.equal(mail);
expect(notification.close.calledWith('asd')).to.be.true;
expect(notification.close.calledOnce).to.be.true;
expect(notificationMock.close.calledWith('asd')).to.be.true;
expect(notificationMock.close.calledOnce).to.be.true;
notification.close.restore();
notificationMock.close.restore();
});
it('should decrypt and focus a read mail', function() {
@ -407,7 +319,7 @@ describe('Mail List controller unit test', function() {
scope.select(mail);
expect(emailDaoMock.decryptBody.calledOnce).to.be.true;
expect(emailMock.decryptBody.calledOnce).to.be.true;
expect(keychainMock.refreshKeyForUserId.calledOnce).to.be.true;
expect(scope.state.mailList.selected).to.equal(mail);
});

View File

@ -1,19 +1,17 @@
'use strict';
var mocks = angular.mock,
NavigationCtrl = require('../../src/js/controller/navigation'),
EmailDAO = require('../../src/js/dao/email-dao'),
OutboxBO = require('../../src/js/bo/outbox'),
appController = require('../../src/js/app-controller');
var NavigationCtrl = require('../../../../src/js/controller/app/navigation'),
Email = require('../../../../src/js/email/email'),
Account = require('../../../../src/js/email/account'),
Outbox = require('../../../../src/js/email/outbox'),
Dialog = require('../../../../src/js/util/dialog'),
Notif = require('../../../../src/js/util/notification');
describe('Navigation Controller unit test', function() {
var scope, ctrl, origEmailDao, emailDaoMock, outboxBoMock, outboxFolder, onConnectStub;
var scope, ctrl, emailDaoMock, accountMock, notificationStub, dialogStub, outboxBoMock, outboxFolder;
beforeEach(function(done) {
// remember original module to restore later
origEmailDao = appController._emailDao;
emailDaoMock = sinon.createStubInstance(EmailDAO);
emailDaoMock._account = {
beforeEach(function() {
var account = {
folders: [{
type: 'Inbox',
count: 2,
@ -24,32 +22,35 @@ describe('Navigation Controller unit test', function() {
path: 'OUTBOX'
}]
};
outboxFolder = emailDaoMock._account.folders[1];
appController._emailDao = emailDaoMock;
outboxBoMock = sinon.createStubInstance(OutboxBO);
appController._outboxBo = outboxBoMock;
outboxBoMock.startChecking.returns();
onConnectStub = sinon.stub(appController, 'onConnect');
onConnectStub.yields();
angular.module('navigationtest', []);
mocks.module('navigationtest');
mocks.inject(function($rootScope, $controller) {
emailDaoMock = sinon.createStubInstance(Email);
outboxFolder = account.folders[1];
outboxBoMock = sinon.createStubInstance(Outbox);
outboxBoMock.startChecking.returns();
dialogStub = sinon.createStubInstance(Dialog);
notificationStub = sinon.createStubInstance(Notif);
accountMock = sinon.createStubInstance(Account);
accountMock.list.returns([account]);
accountMock.isLoggedIn.returns(true);
angular.module('navigationtest', ['woServices', 'woEmail', 'woUtil']);
angular.mock.module('navigationtest');
angular.mock.inject(function($rootScope, $controller) {
scope = $rootScope.$new();
scope.state = {};
ctrl = $controller(NavigationCtrl, {
$scope: scope,
$routeParams: {}
$routeParams: {},
account: accountMock,
email: emailDaoMock,
outbox: outboxBoMock,
notification: notificationStub,
dialog: dialogStub
});
done();
});
});
afterEach(function() {
// restore the module
appController._emailDao = origEmailDao;
onConnectStub.restore();
});
afterEach(function() {});
describe('initial state', function() {
it('should be well defined', function() {

View File

@ -1,48 +1,39 @@
'use strict';
var mocks = angular.mock,
PrivateKeyUploadCtrl = require('../../src/js/controller/privatekey-upload'),
appController = require('../../src/js/app-controller'),
KeychainDAO = require('../../src/js/dao/keychain-dao'),
PGP = require('../../src/js/crypto/pgp');
var PrivateKeyUploadCtrl = require('../../../../src/js/controller/app/privatekey-upload'),
KeychainDAO = require('../../../../src/js/service/keychain'),
PGP = require('../../../../src/js/crypto/pgp'),
Dialog = require('../../../../src/js/util/dialog');
describe('Private Key Upload Controller unit test', function() {
var scope, location, ctrl,
origEmailDao, emailDaoMock,
origKeychain, keychainMock,
pgpStub,
keychainMock, pgpStub, dialogStub,
emailAddress = 'fred@foo.com';
beforeEach(function(done) {
// remember original module to restore later, then replace it
origEmailDao = appController._emailDao;
appController._emailDao = emailDaoMock = {
_account: {
emailAddress: emailAddress
}
};
origKeychain = appController._keychain;
appController._keychain = keychainMock = sinon.createStubInstance(KeychainDAO);
keychainMock._pgp = pgpStub = sinon.createStubInstance(PGP);
beforeEach(function() {
keychainMock = sinon.createStubInstance(KeychainDAO);
pgpStub = sinon.createStubInstance(PGP);
dialogStub = sinon.createStubInstance(Dialog);
angular.module('login-privatekey-download-test', []);
mocks.module('login-privatekey-download-test');
mocks.inject(function($controller, $rootScope) {
angular.module('login-privatekey-download-test', ['woServices']);
angular.mock.module('login-privatekey-download-test');
angular.mock.inject(function($controller, $rootScope) {
scope = $rootScope.$new();
scope.state = {};
ctrl = $controller(PrivateKeyUploadCtrl, {
$location: location,
$scope: scope
$scope: scope,
keychain: keychainMock,
pgp: pgpStub,
dialog: dialogStub,
auth: {
emailAddress: emailAddress
}
});
done();
});
});
afterEach(function() {
// restore the app controller module
appController._keychain = origKeychain;
appController._emailDao = origEmailDao;
});
afterEach(function() {});
describe('checkServerForKey', function() {
var keyParams = {
@ -50,17 +41,14 @@ describe('Private Key Upload Controller unit test', function() {
_id: 'keyId',
};
it('should fail', function(done) {
it('should fail', function() {
pgpStub.getKeyParams.returns(keyParams);
keychainMock.hasPrivateKey.yields(42);
scope.onError = function(err) {
expect(err).to.exist;
expect(keychainMock.hasPrivateKey.calledOnce).to.be.true;
done();
};
scope.checkServerForKey();
expect(dialogStub.error.calledOnce).to.be.true;
expect(keychainMock.hasPrivateKey.calledOnce).to.be.true;
});
it('should return true', function(done) {
@ -165,16 +153,13 @@ describe('Private Key Upload Controller unit test', function() {
});
describe('encryptAndUploadKey', function() {
it('should fail due to keychain.registerDevice', function(done) {
it('should fail due to keychain.registerDevice', function() {
keychainMock.registerDevice.yields(42);
scope.onError = function(err) {
expect(err).to.exist;
expect(keychainMock.registerDevice.calledOnce).to.be.true;
done();
};
scope.encryptAndUploadKey();
expect(dialogStub.error.calledOnce).to.be.true;
expect(keychainMock.registerDevice.calledOnce).to.be.true;
});
it('should work', function(done) {
@ -237,45 +222,36 @@ describe('Private Key Upload Controller unit test', function() {
expect(scope.step).to.equal(2);
});
it('should fail for 3 due to error in setDeviceName', function(done) {
it('should fail for 3 due to error in setDeviceName', function() {
scope.step = 3;
setDeviceNameStub.yields(42);
scope.onError = function(err) {
expect(err).to.exist;
expect(scope.step).to.equal(3);
done();
};
scope.goForward();
expect(dialogStub.error.calledOnce).to.be.true;
expect(scope.step).to.equal(3);
});
it('should fail for 3 due to error in encryptAndUploadKey', function(done) {
it('should fail for 3 due to error in encryptAndUploadKey', function() {
scope.step = 3;
setDeviceNameStub.yields();
encryptAndUploadKeyStub.yields(42);
scope.onError = function(err) {
expect(err).to.exist;
expect(scope.step).to.equal(4);
done();
};
scope.goForward();
expect(dialogStub.error.calledOnce).to.be.true;
expect(scope.step).to.equal(4);
});
it('should work for 3', function(done) {
it('should work for 3', function() {
scope.step = 3;
setDeviceNameStub.yields();
encryptAndUploadKeyStub.yields();
scope.onError = function(err) {
expect(err.title).to.equal('Success');
expect(scope.step).to.equal(4);
done();
};
scope.goForward();
expect(dialogStub.info.calledOnce).to.be.true;
expect(scope.step).to.equal(4);
});
});
});

View File

@ -1,57 +1,49 @@
'use strict';
var mocks = angular.mock,
KeychainDAO = require('../../src/js/dao/keychain-dao'),
InvitationDAO = require('../../src/js/dao/invitation-dao'),
PGP = require('../../src/js/crypto/pgp'),
ReadCtrl = require('../../src/js/controller/read'),
OutboxBO = require('../../src/js/bo/outbox'),
appController = require('../../src/js/app-controller');
var Keychain = require('../../../../src/js/service/keychain'),
InvitationDAO = require('../../../../src/js/service/invitation'),
Email = require('../../../../src/js/email/email'),
PGP = require('../../../../src/js/crypto/pgp'),
ReadCtrl = require('../../../../src/js/controller/app/read'),
Outbox = require('../../../../src/js/email/outbox'),
Dialog = require('../../../../src/js/util/dialog'),
Auth = require('../../../../src/js/service/auth'),
Download = require('../../../../src/js/util/download');
describe('Read Controller unit test', function() {
var scope, ctrl,
origKeychain, keychainMock,
origInvitation, invitationMock,
origCrypto, cryptoMock,
origOutbox, outboxMock,
origEmailDao;
var scope, ctrl, keychainMock, invitationMock, emailMock, pgpMock, outboxMock, dialogMock, authMock, downloadMock,
emailAddress = 'sender@example.com';
beforeEach(function() {
origKeychain = appController._keychain;
appController._keychain = keychainMock = sinon.createStubInstance(KeychainDAO);
keychainMock = sinon.createStubInstance(Keychain);
invitationMock = sinon.createStubInstance(InvitationDAO);
pgpMock = sinon.createStubInstance(PGP);
outboxMock = sinon.createStubInstance(Outbox);
emailMock = sinon.createStubInstance(Email);
dialogMock = sinon.createStubInstance(Dialog);
authMock = sinon.createStubInstance(Auth);
downloadMock = sinon.createStubInstance(Download);
origInvitation = appController._invitationDao;
appController._invitationDao = invitationMock = sinon.createStubInstance(InvitationDAO);
origCrypto = appController._pgp;
appController._pgp = cryptoMock = sinon.createStubInstance(PGP);
origOutbox = appController._outboxBo;
appController._outboxBo = outboxMock = sinon.createStubInstance(OutboxBO);
origEmailDao = appController._emailDao;
appController._emailDao = {
_account: 'sender@example.com'
};
angular.module('readtest', []);
mocks.module('readtest');
mocks.inject(function($rootScope, $controller) {
angular.module('readtest', ['woServices']);
angular.mock.module('readtest');
angular.mock.inject(function($rootScope, $controller) {
scope = $rootScope.$new();
scope.state = {};
ctrl = $controller(ReadCtrl, {
$scope: scope
$scope: scope,
email: emailMock,
invitation: invitationMock,
outbox: outboxMock,
pgp: pgpMock,
keychain: keychainMock,
download: downloadMock,
auth: authMock,
dialog: dialogMock
});
});
});
afterEach(function() {
appController._keychain = origKeychain;
appController._invitationDao = origInvitation;
appController._pgp = origCrypto;
appController._outboxBo = origOutbox;
appController._emailDao = origEmailDao;
});
afterEach(function() {});
describe('scope variables', function() {
it('should be set correctly', function() {
@ -78,23 +70,18 @@ describe('Read Controller unit test', function() {
expect(scope.keyId).to.equal('No key found.');
keychainMock.getReceiverPublicKey.yields(42);
scope.onError = function(err) {
expect(err).to.equal(42);
expect(scope.keyId).to.equal('Searching...');
};
scope.getKeyId(address);
expect(dialogMock.error.calledOnce).to.be.true;
expect(scope.keyId).to.equal('Searching...');
});
it('should allow invitation on empty key', function() {
keychainMock.getReceiverPublicKey.yields();
scope.onError = function(err) {
expect(err).not.exist;
expect(scope.keyId).to.equal('User has no key. Click to invite.');
};
scope.getKeyId(address);
expect(scope.keyId).to.equal('User has no key. Click to invite.');
});
it('should show searching on error', function() {
@ -102,14 +89,10 @@ describe('Read Controller unit test', function() {
publicKey: 'PUBLIC KEY'
});
cryptoMock.getFingerprint.returns('XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX');
scope.onError = function(err) {
expect(err).to.not.exist;
expect(scope.keyId).to.equal('PGP key: XXXXXXXX');
};
pgpMock.getFingerprint.returns('XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX');
scope.getKeyId(address);
expect(scope.keyId).to.equal('PGP key: XXXXXXXX');
});
});
@ -128,39 +111,33 @@ describe('Read Controller unit test', function() {
it('should show error on invitation dao invite error', function() {
invitationMock.invite.yields(42);
scope.onError = function(err) {
expect(err).to.equal(42);
};
scope.invite({
address: 'asdf@asdf.de'
});
expect(dialogMock.error.calledOnce).to.be.true;
});
it('should show error on outbox put error', function() {
invitationMock.invite.yields();
outboxMock.put.yields(42);
scope.onError = function(err) {
expect(err).to.equal(42);
};
scope.invite({
address: 'asdf@asdf.de'
});
expect(dialogMock.error.calledOnce).to.be.true;
});
it('should work', function() {
invitationMock.invite.yields();
outboxMock.put.yields();
scope.onError = function(err) {
expect(err).to.not.exist;
};
scope.invite({
address: 'asdf@asdf.de'
});
expect(dialogMock.error.calledOnce).to.be.true;
});
});

View File

@ -1,31 +1,31 @@
'use strict';
var mocks = angular.mock,
SetPassphraseCtrl = require('../../src/js/controller/set-passphrase'),
PGP = require('../../src/js/crypto/pgp'),
appController = require('../../src/js/app-controller'),
KeychainDAO = require('../../src/js/dao/keychain-dao');
var SetPassphraseCtrl = require('../../../../src/js/controller/app/set-passphrase'),
PGP = require('../../../../src/js/crypto/pgp'),
Keychain = require('../../../../src/js/service/keychain'),
Dialog = require('../../../../src/js/util/dialog');
describe('Set Passphrase Controller unit test', function() {
var scope, setPassphraseCtrl,
dummyFingerprint, expectedFingerprint,
dummyKeyId, expectedKeyId,
emailAddress, keySize, cryptoMock, keychainMock;
emailAddress, keySize, pgpStub, keychainStub, dialogStub;
beforeEach(function() {
appController._pgp = cryptoMock = sinon.createStubInstance(PGP);
appController._keychain = keychainMock = sinon.createStubInstance(KeychainDAO);
pgpStub = sinon.createStubInstance(PGP);
keychainStub = sinon.createStubInstance(Keychain);
dialogStub = sinon.createStubInstance(Dialog);
dummyFingerprint = '3A2D39B4E1404190B8B949DE7D7E99036E712926';
expectedFingerprint = '3A2D 39B4 E140 4190 B8B9 49DE 7D7E 9903 6E71 2926';
dummyKeyId = '9FEB47936E712926';
expectedKeyId = '6E712926';
cryptoMock.getFingerprint.returns(dummyFingerprint);
cryptoMock.getKeyId.returns(dummyKeyId);
pgpStub.getFingerprint.returns(dummyFingerprint);
pgpStub.getKeyId.returns(dummyKeyId);
emailAddress = 'fred@foo.com';
keySize = 1234;
cryptoMock.getKeyParams.returns({
pgpStub.getKeyParams.returns({
_id: dummyKeyId,
fingerprint: dummyFingerprint,
userId: emailAddress,
@ -33,13 +33,16 @@ describe('Set Passphrase Controller unit test', function() {
bitSize: keySize
});
angular.module('setpassphrasetest', []);
mocks.module('setpassphrasetest');
mocks.inject(function($rootScope, $controller) {
angular.module('setpassphrasetest', ['woServices', 'woUtil']);
angular.mock.module('setpassphrasetest');
angular.mock.inject(function($rootScope, $controller) {
scope = $rootScope.$new();
scope.state = {};
setPassphraseCtrl = $controller(SetPassphraseCtrl, {
$scope: scope
$scope: scope,
pgp: pgpStub,
keychain: keychainStub,
dialog: dialogStub
});
});
});
@ -47,33 +50,30 @@ describe('Set Passphrase Controller unit test', function() {
afterEach(function() {});
describe('setPassphrase', function() {
it('should work', function(done) {
it('should work', function() {
scope.oldPassphrase = 'old';
scope.newPassphrase = 'new';
keychainMock.lookupPrivateKey.withArgs(dummyKeyId).yields(null, {
keychainStub.lookupPrivateKey.withArgs(dummyKeyId).yields(null, {
encryptedKey: 'encrypted'
});
cryptoMock.changePassphrase.withArgs({
pgpStub.changePassphrase.withArgs({
privateKeyArmored: 'encrypted',
oldPassphrase: 'old',
newPassphrase: 'new'
}).yields(null, 'newArmoredKey');
keychainMock.saveLocalPrivateKey.withArgs({
keychainStub.saveLocalPrivateKey.withArgs({
_id: dummyKeyId,
userId: emailAddress,
userIds: [],
encryptedKey: 'newArmoredKey'
}).yields();
scope.onError = function(err) {
expect(err.title).to.equal('Success');
done();
};
scope.setPassphrase();
expect(dialogStub.info.calledOnce).to.be.true;
});
});

View File

@ -1,57 +1,50 @@
'use strict';
var mocks = angular.mock,
WriteCtrl = require('../../src/js/controller/write'),
EmailDAO = require('../../src/js/dao/email-dao'),
OutboxBO = require('../../src/js/bo/outbox'),
KeychainDAO = require('../../src/js/dao/keychain-dao'),
appController = require('../../src/js/app-controller');
var WriteCtrl = require('../../../../src/js/controller/app/write'),
Email = require('../../../../src/js/email/email'),
Outbox = require('../../../../src/js/email/outbox'),
Keychain = require('../../../../src/js/service/keychain'),
Auth = require('../../../../src/js/service/auth'),
PGP = require('../../../../src/js/crypto/pgp'),
Dialog = require('../../../../src/js/util/dialog');
describe('Write controller unit test', function() {
var ctrl, scope,
origEmailDao, origOutbox, origKeychain,
emailDaoMock, keychainMock, outboxMock, emailAddress, realname;
authMock, pgpMock, dialogMock, emailMock, keychainMock, outboxMock,
emailAddress, realname;
beforeEach(function() {
// the app controller is a singleton, we need to remember the
// outbox and email dao to restore it after the tests
origEmailDao = appController._emailDao;
origOutbox = appController._outboxBo;
origKeychain = appController._keychain;
outboxMock = sinon.createStubInstance(OutboxBO);
appController._outboxBo = outboxMock;
emailDaoMock = sinon.createStubInstance(EmailDAO);
appController._emailDao = emailDaoMock;
authMock = sinon.createStubInstance(Auth);
pgpMock = sinon.createStubInstance(PGP);
dialogMock = sinon.createStubInstance(Dialog);
outboxMock = sinon.createStubInstance(Outbox);
emailMock = sinon.createStubInstance(Email);
keychainMock = sinon.createStubInstance(Keychain);
emailAddress = 'fred@foo.com';
realname = 'Fred Foo';
emailDaoMock._account = {
emailAddress: emailAddress,
realname: realname
};
authMock.emailAddress = emailAddress;
authMock.realname = realname;
keychainMock = sinon.createStubInstance(KeychainDAO);
appController._keychain = keychainMock;
angular.module('writetest', []);
mocks.module('writetest');
mocks.inject(function($rootScope, $controller) {
angular.module('writetest', ['woEmail', 'woServices', 'woUtil']);
angular.mock.module('writetest');
angular.mock.inject(function($rootScope, $controller) {
scope = $rootScope.$new();
scope.state = {};
ctrl = $controller(WriteCtrl, {
$scope: scope
$scope: scope,
auth: authMock,
keychain: keychainMock,
pgp: pgpMock,
email: emailMock,
outbox: outboxMock,
dialog: dialogMock
});
});
});
afterEach(function() {
// restore the app controller
appController._emailDao = origEmailDao;
appController._outboxBo = origOutbox;
appController._keychain = origKeychain;
});
afterEach(function() {});
describe('scope variables', function() {
it('should be set correctly', function() {
@ -187,7 +180,7 @@ describe('Write controller unit test', function() {
expect(keychainMock.getReceiverPublicKey.called).to.be.false;
});
it('should not work for error in keychain', function(done) {
it('should not work for error in keychain', function() {
var recipient = {
address: 'asds@example.com'
};
@ -198,15 +191,13 @@ describe('Write controller unit test', function() {
errMsg: '404 not found yadda yadda'
});
scope.onError = function() {
expect(recipient.key).to.be.undefined;
expect(recipient.secure).to.be.false;
expect(scope.checkSendStatus.callCount).to.equal(1);
expect(keychainMock.refreshKeyForUserId.calledOnce).to.be.true;
done();
};
scope.verify(recipient);
expect(dialogMock.error.calledOnce).to.be.true;
expect(recipient.key).to.be.undefined;
expect(recipient.secure).to.be.false;
expect(scope.checkSendStatus.callCount).to.equal(1);
expect(keychainMock.refreshKeyForUserId.calledOnce).to.be.true;
});
it('should work for main userId', function(done) {
@ -337,6 +328,7 @@ describe('Write controller unit test', function() {
address: emailAddress,
name: realname
}]);
expect(mail.to).to.deep.equal(scope.to);
expect(mail.cc).to.deep.equal(scope.cc);
expect(mail.bcc).to.deep.equal(scope.bcc);
@ -345,19 +337,14 @@ describe('Write controller unit test', function() {
expect(mail.attachments).to.be.empty;
expect(mail.sentDate).to.exist;
return true;
})).yields();
emailDaoMock.setFlags.yields();
scope.onError = function(err) {
expect(err).to.not.exist;
};
emailMock.setFlags.yields();
scope.sendToOutbox();
expect(outboxMock.put.calledOnce).to.be.true;
expect(emailDaoMock.setFlags.calledOnce).to.be.true;
expect(emailMock.setFlags.calledOnce).to.be.true;
expect(scope.state.lightbox).to.be.undefined;
expect(scope.replyTo.answered).to.be.true;
});

View File

@ -1,22 +1,21 @@
'use strict';
var mocks = angular.mock,
AddAccountCtrl = require('../../src/js/controller/add-account'),
Auth = require('../../src/js/bo/auth'),
appController = require('../../src/js/app-controller'),
cfg = require('../../src/js/app-config').config;
var AddAccountCtrl = require('../../../../src/js/controller/login/add-account'),
Auth = require('../../../../src/js/service/auth'),
Dialog = require('../../../../src/js/util/dialog'),
cfg = require('../../../../src/js/app-config').config;
describe('Add Account Controller unit test', function() {
var scope, location, mailConfigMock, ctrl, authStub, origAuth;
var scope, location, mailConfigMock, ctrl, authStub, dialogStub;
beforeEach(function() {
// remember original module to restore later, then replace it
origAuth = appController._auth;
appController._auth = authStub = sinon.createStubInstance(Auth);
authStub = sinon.createStubInstance(Auth);
dialogStub = sinon.createStubInstance(Dialog);
angular.module('addaccounttest', ['woServices']);
mocks.module('addaccounttest');
mocks.inject(function($controller, $rootScope, $location, mailConfig) {
angular.mock.module('addaccounttest');
angular.mock.inject(function($controller, $rootScope, $location, mailConfig) {
location = $location;
mailConfigMock = mailConfig;
scope = $rootScope.$new();
@ -31,15 +30,14 @@ describe('Add Account Controller unit test', function() {
$location: location,
$scope: scope,
$routeParams: {},
mailConfig: mailConfigMock
mailConfig: mailConfigMock,
auth: authStub,
dialog: dialogStub
});
});
});
afterEach(function() {
// restore the app controller module
appController._auth = origAuth;
location.path.restore();
location.search.restore();
if (scope.$apply.restore) {
@ -119,7 +117,7 @@ describe('Add Account Controller unit test', function() {
});
it('should use oauth', function() {
scope.onError = function(options) {
dialogStub.confirm = function(options) {
options.callback(true);
};
authStub.getOAuthToken.yields();
@ -131,7 +129,7 @@ describe('Add Account Controller unit test', function() {
});
it('should not use oauth', function() {
scope.onError = function(options) {
dialogStub.confirm = function(options) {
options.callback(false);
};
@ -141,19 +139,16 @@ describe('Add Account Controller unit test', function() {
expect(authStub.getOAuthToken.called).to.be.false;
});
it('should not forward to login when oauth fails', function(done) {
scope.onError = function(options) {
scope.onError = function(err) {
expect(err).to.exist;
expect(setCredentialsStub.called).to.be.false;
done();
};
it('should not forward to login when oauth fails', function() {
dialogStub.confirm = function(options) {
options.callback(true);
};
authStub.getOAuthToken.yields(new Error());
scope.oauthPossible();
expect(dialogStub.error.calledOnce).to.be.true;
expect(setCredentialsStub.called).to.be.false;
});
});

View File

@ -1,22 +1,20 @@
'use strict';
var mocks = angular.mock,
CreateAccountCtrl = require('../../src/js/controller/create-account'),
AdminDao = require('../../src/js/dao/admin-dao'),
appController = require('../../src/js/app-controller');
var CreateAccountCtrl = require('../../../../src/js/controller/login/create-account'),
AdminDao = require('../../../../src/js/service/admin'),
Auth = require('../../../../src/js/service/auth');
describe('Create Account Controller unit test', function() {
var scope, location, ctrl, authStub, origAuth, adminStub;
var scope, location, ctrl, authStub, adminStub;
beforeEach(function() {
// remember original module to restore later, then replace it
origAuth = appController._auth;
appController._auth = authStub = {};
appController._adminDao = adminStub = sinon.createStubInstance(AdminDao);
adminStub = sinon.createStubInstance(AdminDao);
authStub = sinon.createStubInstance(Auth);
angular.module('createaccounttest', []);
mocks.module('createaccounttest');
mocks.inject(function($controller, $rootScope, $location) {
angular.module('createaccounttest', ['woServices', 'woAppConfig']);
angular.mock.module('createaccounttest');
angular.mock.inject(function($controller, $rootScope, $location) {
location = $location;
scope = $rootScope.$new();
scope.state = {};
@ -30,15 +28,14 @@ describe('Create Account Controller unit test', function() {
ctrl = $controller(CreateAccountCtrl, {
$location: location,
$scope: scope,
$routeParams: {}
$routeParams: {},
auth: authStub,
admin: adminStub
});
});
});
afterEach(function() {
// restore the app controller module
appController._auth = origAuth;
location.path.restore();
location.search.restore();
if (scope.$apply.restore) {

View File

@ -0,0 +1,186 @@
'use strict';
var LoginCtrl = require('../../../../src/js/controller/login/login'),
Email = require('../../../../src/js/email/email'),
Account = require('../../../../src/js/email/account'),
Dialog = require('../../../../src/js/util/dialog'),
UpdateHandler = require('../../../../src/js/util/update/update-handler'),
Auth = require('../../../../src/js/service/auth'),
Keychain = require('../../../../src/js/service/keychain');
describe('Login Controller unit test', function() {
var scope, location, ctrl,
emailMock, keychainMock, authMock, accountMock, dialogMock, updateHandlerMock, goToStub,
emailAddress = 'fred@foo.com';
beforeEach(function() {
emailMock = sinon.createStubInstance(Email);
accountMock = sinon.createStubInstance(Account);
authMock = sinon.createStubInstance(Auth);
keychainMock = sinon.createStubInstance(Keychain);
dialogMock = sinon.createStubInstance(Dialog);
updateHandlerMock = sinon.createStubInstance(UpdateHandler);
location = {
path: function() {}
};
authMock.emailAddress = emailAddress;
});
function createController() {
angular.module('login-test', ['woServices', 'woEmail', 'woUtil']);
angular.mock.module('login-test');
angular.mock.inject(function($rootScope, $controller) {
scope = $rootScope.$new();
scope.state = {};
scope.form = {};
scope.goTo = function() {};
goToStub = sinon.stub(scope, 'goTo');
ctrl = $controller(LoginCtrl, {
$scope: scope,
$location: location,
updateHandler: updateHandlerMock,
account: accountMock,
auth: authMock,
email: emailMock,
keychain: keychainMock,
dialog: dialogMock
});
});
}
afterEach(function() {});
it('should fail for auth.getEmailAddress', function() {
authMock.getEmailAddress.yields(new Error());
createController();
expect(updateHandlerMock.checkForUpdate.calledOnce).to.be.true;
expect(authMock.init.calledOnce).to.be.true;
expect(dialogMock.error.calledOnce).to.be.true;
});
it('should redirect to /add-account', function() {
authMock.getEmailAddress.yields(null, {});
createController();
expect(goToStub.withArgs('/add-account').calledOnce).to.be.true;
});
it('should fail for auth.init', function() {
authMock.getEmailAddress.yields(null, {
emailAddress: emailAddress
});
accountMock.init.yields(new Error());
createController();
expect(accountMock.init.calledOnce).to.be.true;
expect(dialogMock.error.calledOnce).to.be.true;
});
it('should redirect to /login-existing', function() {
authMock.getEmailAddress.yields(null, {
emailAddress: emailAddress
});
accountMock.init.yields(null, {
publicKey: 'publicKey',
privateKey: 'privateKey'
});
emailMock.unlock.yields(new Error());
createController();
expect(goToStub.withArgs('/login-existing').calledOnce).to.be.true;
});
it('should fail for auth.storeCredentials', function() {
authMock.getEmailAddress.yields(null, {
emailAddress: emailAddress
});
accountMock.init.yields(null, {
publicKey: 'publicKey',
privateKey: 'privateKey'
});
emailMock.unlock.yields();
authMock.storeCredentials.yields(new Error());
createController();
expect(dialogMock.error.calledOnce).to.be.true;
});
it('should redirect to /desktop', function() {
authMock.getEmailAddress.yields(null, {
emailAddress: emailAddress
});
accountMock.init.yields(null, {
publicKey: 'publicKey',
privateKey: 'privateKey'
});
emailMock.unlock.yields();
authMock.storeCredentials.yields();
createController();
expect(goToStub.withArgs('/desktop').calledOnce).to.be.true;
});
it('should fail for keychain.requestPrivateKeyDownload', function() {
authMock.getEmailAddress.yields(null, {
emailAddress: emailAddress
});
accountMock.init.yields(null, {
publicKey: 'publicKey'
});
keychainMock.requestPrivateKeyDownload.yields(new Error());
createController();
expect(dialogMock.error.calledOnce).to.be.true;
});
it('should redirect to /login-privatekey-download', function() {
authMock.getEmailAddress.yields(null, {
emailAddress: emailAddress
});
accountMock.init.yields(null, {
publicKey: 'publicKey'
});
keychainMock.requestPrivateKeyDownload.yields(null, true);
createController();
expect(goToStub.withArgs('/login-privatekey-download').calledOnce).to.be.true;
});
it('should redirect to /login-new-device', function() {
authMock.getEmailAddress.yields(null, {
emailAddress: emailAddress
});
accountMock.init.yields(null, {
publicKey: 'publicKey'
});
keychainMock.requestPrivateKeyDownload.yields();
createController();
expect(goToStub.withArgs('/login-new-device').calledOnce).to.be.true;
});
it('should redirect to /login-initial', function() {
authMock.getEmailAddress.yields(null, {
emailAddress: emailAddress
});
accountMock.init.yields(null, {});
createController();
expect(goToStub.withArgs('/login-initial').calledOnce).to.be.true;
});
});

View File

@ -1,53 +1,41 @@
'use strict';
var Auth = require('../../src/js/bo/auth'),
mocks = angular.mock,
LoginExistingCtrl = require('../../src/js/controller/login-existing'),
EmailDAO = require('../../src/js/dao/email-dao'),
KeychainDAO = require('../../src/js/dao/keychain-dao'),
appController = require('../../src/js/app-controller');
var Auth = require('../../../../src/js/service/auth'),
LoginExistingCtrl = require('../../../../src/js/controller/login/login-existing'),
EmailDAO = require('../../../../src/js/email/email'),
KeychainDAO = require('../../../../src/js/service/keychain');
describe('Login (existing user) Controller unit test', function() {
var scope, location, ctrl, origEmailDao, emailDaoMock,
origAuth, authMock,
var scope, location, ctrl, emailDaoMock, authMock,
emailAddress = 'fred@foo.com',
passphrase = 'asd',
keychainMock;
beforeEach(function() {
// remember original module to restore later
origEmailDao = appController._emailDao;
origAuth = appController._auth;
appController._emailDao = emailDaoMock = sinon.createStubInstance(EmailDAO);
appController._auth = authMock = sinon.createStubInstance(Auth);
emailDaoMock = sinon.createStubInstance(EmailDAO);
authMock = sinon.createStubInstance(Auth);
keychainMock = sinon.createStubInstance(KeychainDAO);
emailDaoMock._keychain = keychainMock;
emailDaoMock._account = {
emailAddress: emailAddress,
};
authMock.emailAddress = emailAddress;
angular.module('loginexistingtest', []);
mocks.module('loginexistingtest');
mocks.inject(function($rootScope, $controller, $location) {
angular.module('loginexistingtest', ['woServices']);
angular.mock.module('loginexistingtest');
angular.mock.inject(function($rootScope, $controller, $location) {
location = $location;
scope = $rootScope.$new();
scope.state = {};
scope.form = {};
ctrl = $controller(LoginExistingCtrl, {
$scope: scope,
$routeParams: {}
$routeParams: {},
email: emailDaoMock,
auth: authMock,
keychain: keychainMock
});
});
});
afterEach(function() {
// restore the module
appController._emailDao = origEmailDao;
appController._auth = origAuth;
});
afterEach(function() {});
describe('initial state', function() {
it('should be well defined', function() {

View File

@ -1,39 +1,26 @@
'use strict';
var Auth = require('../../src/js/bo/auth'),
mocks = angular.mock,
LoginInitialCtrl = require('../../src/js/controller/login-initial'),
PGP = require('../../src/js/crypto/pgp'),
EmailDAO = require('../../src/js/dao/email-dao'),
appController = require('../../src/js/app-controller');
var Auth = require('../../../../src/js/service/auth'),
LoginInitialCtrl = require('../../../../src/js/controller/login/login-initial'),
Email = require('../../../../src/js/email/email');
describe('Login (initial user) Controller unit test', function() {
var scope, ctrl, location, origEmailDao, emailDaoMock,
origAuth, authMock, newsletterStub,
var scope, ctrl, location, emailMock, authMock, newsletterStub,
emailAddress = 'fred@foo.com',
keyId, expectedKeyId,
cryptoMock;
keyId, expectedKeyId;
beforeEach(function() {
// remember original module to restore later
origEmailDao = appController._emailDao;
origAuth = appController._auth;
appController._emailDao = emailDaoMock = sinon.createStubInstance(EmailDAO);
appController._auth = authMock = sinon.createStubInstance(Auth);
emailMock = sinon.createStubInstance(Email);
authMock = sinon.createStubInstance(Auth);
keyId = '9FEB47936E712926';
expectedKeyId = '6E712926';
cryptoMock = sinon.createStubInstance(PGP);
emailDaoMock._crypto = cryptoMock;
emailDaoMock._account = {
emailAddress: emailAddress,
};
authMock.emailAddress = emailAddress;
angular.module('logininitialtest', ['woServices']);
mocks.module('logininitialtest');
mocks.inject(function($rootScope, $controller, $location, newsletter) {
angular.mock.module('logininitialtest');
angular.mock.inject(function($rootScope, $controller, $location, newsletter) {
scope = $rootScope.$new();
location = $location;
newsletterStub = sinon.stub(newsletter, 'signup');
@ -43,16 +30,14 @@ describe('Login (initial user) Controller unit test', function() {
ctrl = $controller(LoginInitialCtrl, {
$scope: scope,
$routeParams: {},
newsletter: newsletter
newsletter: newsletter,
email: emailMock,
auth: authMock
});
});
});
afterEach(function() {
// restore the module
appController._emailDao = origEmailDao;
appController._auth = origAuth;
});
afterEach(function() {});
describe('initial state', function() {
it('should be well defined', function() {
@ -92,7 +77,7 @@ describe('Login (initial user) Controller unit test', function() {
it('should fail due to error in emailDao.unlock', function() {
scope.agree = true;
emailDaoMock.unlock.withArgs({
emailMock.unlock.withArgs({
passphrase: undefined
}).yields(new Error('asdf'));
authMock.storeCredentials.yields();
@ -107,7 +92,7 @@ describe('Login (initial user) Controller unit test', function() {
it('should unlock crypto', function() {
scope.agree = true;
emailDaoMock.unlock.withArgs({
emailMock.unlock.withArgs({
passphrase: undefined
}).yields();
authMock.storeCredentials.yields();
@ -118,7 +103,7 @@ describe('Login (initial user) Controller unit test', function() {
expect(scope.state.ui).to.equal(2);
expect(newsletterStub.called).to.be.true;
expect(location.$$path).to.equal('/desktop');
expect(emailDaoMock.unlock.calledOnce).to.be.true;
expect(emailMock.unlock.calledOnce).to.be.true;
});
});
});

View File

@ -1,38 +1,32 @@
'use strict';
var mocks = angular.mock,
PGP = require('../../src/js/crypto/pgp'),
LoginNewDeviceCtrl = require('../../src/js/controller/login-new-device'),
KeychainDAO = require('../../src/js/dao/keychain-dao'),
EmailDAO = require('../../src/js/dao/email-dao'),
appController = require('../../src/js/app-controller');
var PGP = require('../../../../src/js/crypto/pgp'),
LoginNewDeviceCtrl = require('../../../../src/js/controller/login/login-new-device'),
KeychainDAO = require('../../../../src/js/service/keychain'),
EmailDAO = require('../../../../src/js/email/email'),
Auth = require('../../../../src/js/service/auth');
describe('Login (new device) Controller unit test', function() {
var scope, ctrl, origEmailDao, emailDaoMock, pgpMock,
var scope, ctrl, emailMock, pgpMock, authMock,
emailAddress = 'fred@foo.com',
passphrase = 'asd',
keyId,
keychainMock;
beforeEach(function() {
// remember original module to restore later
origEmailDao = appController._emailDao;
emailDaoMock = sinon.createStubInstance(EmailDAO);
appController._emailDao = emailDaoMock;
emailMock = sinon.createStubInstance(EmailDAO);
authMock = sinon.createStubInstance(Auth);
keyId = '9FEB47936E712926';
emailDaoMock._keychain = keychainMock = sinon.createStubInstance(KeychainDAO);
appController._pgp = pgpMock = sinon.createStubInstance(PGP);
keychainMock = sinon.createStubInstance(KeychainDAO);
pgpMock = sinon.createStubInstance(PGP);
pgpMock.extractPublicKey.returns('publicKeyArmored');
emailDaoMock._account = {
emailAddress: emailAddress,
};
authMock.emailAddress = emailAddress;
angular.module('loginnewdevicetest', []);
mocks.module('loginnewdevicetest');
mocks.inject(function($rootScope, $controller) {
angular.module('loginnewdevicetest', ['woServices']);
angular.mock.module('loginnewdevicetest');
angular.mock.inject(function($rootScope, $controller) {
scope = $rootScope.$new();
scope.state = {
ui: {}
@ -40,15 +34,16 @@ describe('Login (new device) Controller unit test', function() {
scope.form = {};
ctrl = $controller(LoginNewDeviceCtrl, {
$scope: scope,
$routeParams: {}
$routeParams: {},
email: emailMock,
auth: authMock,
pgp: pgpMock,
keychain: keychainMock
});
});
});
afterEach(function() {
// restore the module
appController._emailDao = origEmailDao;
});
afterEach(function() {});
describe('initial state', function() {
it('should be well defined', function() {
@ -73,12 +68,12 @@ describe('Login (new device) Controller unit test', function() {
_id: keyId,
publicKey: 'a'
});
emailDaoMock.unlock.withArgs(sinon.match.any, passphrase).yields();
emailMock.unlock.withArgs(sinon.match.any, passphrase).yields();
keychainMock.putUserKeyPair.yields();
scope.confirmPassphrase();
expect(emailDaoMock.unlock.calledOnce).to.be.true;
expect(emailMock.unlock.calledOnce).to.be.true;
expect(keychainMock.getUserKeyPair.calledOnce).to.be.true;
});
@ -95,12 +90,12 @@ describe('Login (new device) Controller unit test', function() {
});
keychainMock.getUserKeyPair.withArgs(emailAddress).yields();
emailDaoMock.unlock.withArgs(sinon.match.any, passphrase).yields();
emailMock.unlock.withArgs(sinon.match.any, passphrase).yields();
keychainMock.putUserKeyPair.yields();
scope.confirmPassphrase();
expect(emailDaoMock.unlock.calledOnce).to.be.true;
expect(emailMock.unlock.calledOnce).to.be.true;
expect(keychainMock.getUserKeyPair.calledOnce).to.be.true;
});
@ -119,7 +114,7 @@ describe('Login (new device) Controller unit test', function() {
_id: keyId,
publicKey: 'a'
});
emailDaoMock.unlock.yields();
emailMock.unlock.yields();
keychainMock.putUserKeyPair.yields({
errMsg: 'yo mamma.'
});
@ -127,7 +122,7 @@ describe('Login (new device) Controller unit test', function() {
scope.confirmPassphrase();
expect(keychainMock.getUserKeyPair.calledOnce).to.be.true;
expect(emailDaoMock.unlock.calledOnce).to.be.true;
expect(emailMock.unlock.calledOnce).to.be.true;
expect(keychainMock.putUserKeyPair.calledOnce).to.be.true;
expect(scope.errMsg).to.equal('yo mamma.');
});
@ -147,7 +142,7 @@ describe('Login (new device) Controller unit test', function() {
_id: keyId,
publicKey: 'a'
});
emailDaoMock.unlock.yields({
emailMock.unlock.yields({
errMsg: 'yo mamma.'
});
@ -155,7 +150,7 @@ describe('Login (new device) Controller unit test', function() {
expect(scope.incorrect).to.be.true;
expect(keychainMock.getUserKeyPair.calledOnce).to.be.true;
expect(emailDaoMock.unlock.calledOnce).to.be.true;
expect(emailMock.unlock.calledOnce).to.be.true;
expect(scope.errMsg).to.equal('yo mamma.');
});

View File

@ -1,55 +1,43 @@
'use strict';
var mocks = angular.mock,
Auth = require('../../src/js/bo/auth'),
LoginPrivateKeyDownloadCtrl = require('../../src/js/controller/login-privatekey-download'),
EmailDAO = require('../../src/js/dao/email-dao'),
appController = require('../../src/js/app-controller'),
KeychainDAO = require('../../src/js/dao/keychain-dao');
var Auth = require('../../../../src/js/service/auth'),
LoginPrivateKeyDownloadCtrl = require('../../../../src/js/controller/login/login-privatekey-download'),
Email = require('../../../../src/js/email/email'),
Keychain = require('../../../../src/js/service/keychain');
describe('Login Private Key Download Controller unit test', function() {
var scope, location, ctrl,
origEmailDao, emailDaoMock,
origAuth, authMock,
origKeychain, keychainMock,
emailDaoMock, authMock, keychainMock,
emailAddress = 'fred@foo.com';
beforeEach(function(done) {
// remember original module to restore later, then replace it
origEmailDao = appController._emailDao;
origKeychain = appController._keychain;
origAuth = appController._auth;
emailDaoMock = sinon.createStubInstance(Email);
keychainMock = sinon.createStubInstance(Keychain);
authMock = sinon.createStubInstance(Auth);
appController._emailDao = emailDaoMock = sinon.createStubInstance(EmailDAO);
appController._keychain = keychainMock = sinon.createStubInstance(KeychainDAO);
appController._auth = authMock = sinon.createStubInstance(Auth);
authMock.emailAddress = emailAddress;
emailDaoMock._account = {
emailAddress: emailAddress
};
angular.module('login-privatekey-download-test', []);
mocks.module('login-privatekey-download-test');
mocks.inject(function($controller, $rootScope) {
angular.module('login-privatekey-download-test', ['woServices']);
angular.mock.module('login-privatekey-download-test');
angular.mock.inject(function($controller, $rootScope, $location) {
scope = $rootScope.$new();
scope.state = {};
scope.tokenForm = {};
scope.codeForm = {};
location = $location;
ctrl = $controller(LoginPrivateKeyDownloadCtrl, {
$location: location,
$scope: scope,
$routeParams: {}
$routeParams: {},
auth: authMock,
email: emailDaoMock,
keychain: keychainMock
});
done();
});
});
afterEach(function() {
// restore the app controller module
appController._emailDao = origEmailDao;
appController._keychain = origKeychain;
appController._auth = origAuth;
});
afterEach(function() {});
describe('initialization', function() {
it('should work', function() {
@ -208,20 +196,9 @@ describe('Login Private Key Download Controller unit test', function() {
});
describe('goTo', function() {
it('should work', function(done) {
mocks.inject(function($controller, $rootScope, $location) {
location = $location;
sinon.stub(location, 'path', function(path) {
expect(path).to.equal('/desktop');
done();
});
scope = $rootScope.$new();
scope.state = {};
ctrl = $controller(LoginPrivateKeyDownloadCtrl, {
$location: location,
$scope: scope,
$routeParams: {}
});
it('should work', function() {
sinon.stub(location, 'path', function(path) {
expect(path).to.equal('/desktop');
});
scope.goTo('/desktop');

View File

@ -1,32 +1,28 @@
'use strict';
var mocks = angular.mock,
Auth = require('../../src/js/bo/auth'),
ConnectionDoctor = require('../../src/js/util/connection-doctor'),
SetCredentialsCtrl = require('../../src/js/controller/login-set-credentials'),
appController = require('../../src/js/app-controller');
var Auth = require('../../../../src/js/service/auth'),
ConnectionDoctor = require('../../../../src/js/util/connection-doctor'),
SetCredentialsCtrl = require('../../../../src/js/controller/login/login-set-credentials');
describe('Login (Set Credentials) Controller unit test', function() {
// Angular parameters
var scope, location, provider;
// Stubs
var auth, origAuth, doctor, origDoctor;
var auth, doctor;
// SUT
var setCredentialsCtrl;
beforeEach(function() {
// remeber pre-test state to restore later
origAuth = appController._auth;
origDoctor = appController._doctor;
auth = appController._auth = sinon.createStubInstance(Auth);
doctor = appController._doctor = sinon.createStubInstance(ConnectionDoctor);
auth = sinon.createStubInstance(Auth);
doctor = sinon.createStubInstance(ConnectionDoctor);
// setup the controller
angular.module('setcredentialstest', []);
mocks.module('setcredentialstest');
mocks.inject(function($rootScope, $controller, $location) {
angular.mock.module('setcredentialstest');
angular.mock.inject(function($rootScope, $controller, $location) {
scope = $rootScope.$new();
location = $location;
location.search({
@ -43,16 +39,14 @@ describe('Login (Set Credentials) Controller unit test', function() {
setCredentialsCtrl = $controller(SetCredentialsCtrl, {
$scope: scope,
$routeParams: {}
$routeParams: {},
auth: auth,
connectionDoctor: doctor
});
});
});
afterEach(function() {
// restore pre-test state
appController._auth = origAuth;
appController._doctor = origDoctor;
});
afterEach(function() {});
describe('set credentials', function() {
it('should work', function() {

View File

@ -1,23 +1,19 @@
'use strict';
var mocks = angular.mock,
ValidatePhoneCtrl = require('../../src/js/controller/validate-phone'),
Auth = require('../../src/js/bo/auth'),
AdminDao = require('../../src/js/dao/admin-dao'),
appController = require('../../src/js/app-controller');
var ValidatePhoneCtrl = require('../../../../src/js/controller/login/validate-phone'),
Auth = require('../../../../src/js/service/auth'),
AdminDao = require('../../../../src/js/service/admin');
describe('Validate Phone Controller unit test', function() {
var scope, location, mailConfigMock, ctrl, authStub, origAuth, adminStub;
var scope, location, mailConfigMock, ctrl, authStub, adminStub;
beforeEach(function() {
// remember original module to restore later, then replace it
origAuth = appController._auth;
appController._auth = authStub = sinon.createStubInstance(Auth);
appController._adminDao = adminStub = sinon.createStubInstance(AdminDao);
authStub = sinon.createStubInstance(Auth);
adminStub = sinon.createStubInstance(AdminDao);
angular.module('validatephonetest', ['woServices']);
mocks.module('validatephonetest');
mocks.inject(function($controller, $rootScope, $location, mailConfig) {
angular.mock.module('validatephonetest');
angular.mock.inject(function($controller, $rootScope, $location, mailConfig) {
location = $location;
mailConfigMock = mailConfig;
scope = $rootScope.$new();
@ -38,15 +34,14 @@ describe('Validate Phone Controller unit test', function() {
$location: location,
$scope: scope,
$routeParams: {},
mailConfig: mailConfigMock
mailConfig: mailConfigMock,
auth: authStub,
admin: adminStub
});
});
});
afterEach(function() {
// restore the app controller module
appController._auth = origAuth;
location.path.restore();
location.search.restore();
if (scope.$apply.restore) {

View File

@ -1,7 +1,7 @@
'use strict';
var Crypto = require('../../src/js/crypto/crypto'),
config = require('../../src/js/app-config').config,
var Crypto = require('../../../src/js/crypto/crypto'),
config = require('../../../src/js/app-config').config,
util = require('crypto-lib').util;
describe('Crypto unit tests', function() {

View File

@ -1,6 +1,6 @@
'use strict';
var PGP = require('../../src/js/crypto/pgp');
var PGP = require('../../../src/js/crypto/pgp');
describe('PGP Crypto Api unit tests', function() {
this.timeout(20000);

View File

@ -0,0 +1,275 @@
'use strict';
var Account = require('../../../src/js/email/account'),
appConfig = require('../../../src/js/app-config'),
Auth = require('../../../src/js/service/auth'),
DeviceStorageDAO = require('../../../src/js/service/devicestorage'),
Email = require('../../../src/js/email/email'),
Outbox = require('../../../src/js/email/outbox'),
Keychain = require('../../../src/js/service/keychain'),
UpdateHandler = require('../../../src/js/util/update/update-handler'),
Dialog = require('../../../src/js/util/dialog');
describe('Account Service unit test', function() {
var account, authStub, outboxStub, emailStub, devicestorageStub, keychainStub, updateHandlerStub, pgpbuilderStub, dialogStub,
realname = 'John Doe',
dummyUser = 'spiderpig@springfield.com';
chai.config.includeStack = true;
beforeEach(function() {
authStub = sinon.createStubInstance(Auth);
outboxStub = sinon.createStubInstance(Outbox);
devicestorageStub = sinon.createStubInstance(DeviceStorageDAO);
emailStub = sinon.createStubInstance(Email);
outboxStub = sinon.createStubInstance(Outbox);
keychainStub = sinon.createStubInstance(Keychain);
updateHandlerStub = sinon.createStubInstance(UpdateHandler);
pgpbuilderStub = {};
dialogStub = sinon.createStubInstance(Dialog);
account = new Account(appConfig, authStub, devicestorageStub, emailStub, outboxStub, keychainStub, updateHandlerStub, pgpbuilderStub, dialogStub);
});
afterEach(function() {});
describe('isLoggedIn', function() {
it('should be logged in', function() {
account._accounts = [{}];
expect(account.isLoggedIn()).to.be.true;
});
it('should not be logged in', function() {
account._accounts = [];
expect(account.isLoggedIn()).to.be.false;
});
});
describe('list', function() {
it('should work', function() {
var testAccounts = [{
foo: 'bar'
}];
account._accounts = testAccounts;
expect(account.list()).to.deep.equal(testAccounts);
});
});
describe('init', function() {
it('should fail for invalid email address', function() {
account.init({
emailAddress: dummyUser.replace('@'),
realname: realname
}, onInit);
function onInit(err, keys) {
expect(err).to.exist;
expect(keys).to.not.exist;
}
});
it('should fail for _accountStore.init', function() {
devicestorageStub.init.throws(new Error('asdf'));
account.init({
emailAddress: dummyUser,
realname: realname
}, onInit);
function onInit(err, keys) {
expect(err.message).to.match(/asdf/);
expect(keys).to.not.exist;
}
});
it('should fail for _updateHandler.update', function() {
updateHandlerStub.update.yields(new Error('asdf'));
account.init({
emailAddress: dummyUser,
realname: realname
}, onInit);
function onInit(err, keys) {
expect(err.message).to.match(/Updating/);
expect(keys).to.not.exist;
}
});
it('should fail for _keychain.getUserKeyPair', function() {
updateHandlerStub.update.yields();
keychainStub.getUserKeyPair.yields(new Error('asdf'));
account.init({
emailAddress: dummyUser,
realname: realname
}, onInit);
function onInit(err, keys) {
expect(err.message).to.match(/asdf/);
expect(keys).to.not.exist;
}
});
it('should fail for _keychain.refreshKeyForUserId', function() {
var storedKeys = {
publicKey: 'publicKey'
};
updateHandlerStub.update.yields();
keychainStub.getUserKeyPair.yields(null, storedKeys);
keychainStub.refreshKeyForUserId.yields(new Error('asdf'));
account.init({
emailAddress: dummyUser,
realname: realname
}, onInit);
function onInit(err, keys) {
expect(err.message).to.match(/asdf/);
expect(keys).to.not.exist;
}
});
it('should fail for _emailDao.init after _keychain.refreshKeyForUserId', function() {
var storedKeys = {
publicKey: 'publicKey'
};
updateHandlerStub.update.yields();
keychainStub.getUserKeyPair.yields(null, storedKeys);
keychainStub.refreshKeyForUserId.yields(null, storedKeys);
emailStub.init.yields(new Error('asdf'));
account.init({
emailAddress: dummyUser,
realname: realname
}, onInit);
function onInit(err, keys) {
expect(err.message).to.match(/asdf/);
expect(keys).to.not.exist;
}
});
it('should fail for _emailDao.init', function() {
var storedKeys = {
publicKey: 'publicKey',
privateKey: 'privateKey'
};
updateHandlerStub.update.yields();
keychainStub.getUserKeyPair.yields(null, storedKeys);
emailStub.init.yields(new Error('asdf'));
account.init({
emailAddress: dummyUser,
realname: realname
}, onInit);
function onInit(err, keys) {
expect(err.message).to.match(/asdf/);
expect(keys).to.not.exist;
}
});
it('should work after _keychain.refreshKeyForUserId', function() {
var storedKeys = {
publicKey: 'publicKey'
};
updateHandlerStub.update.yields();
keychainStub.getUserKeyPair.yields(null, storedKeys);
keychainStub.refreshKeyForUserId.yields(null, 'publicKey');
emailStub.init.yields();
account.init({
emailAddress: dummyUser,
realname: realname
}, onInit);
function onInit(err, keys) {
expect(err).to.not.exist;
expect(keys).to.deep.equal(storedKeys);
}
});
it('should work', function() {
var storedKeys = {
publicKey: 'publicKey',
privateKey: 'privateKey'
};
updateHandlerStub.update.yields();
keychainStub.getUserKeyPair.yields(null, storedKeys);
emailStub.init.yields();
account.init({
emailAddress: dummyUser,
realname: realname
}, onInit);
function onInit(err, keys) {
expect(err).to.not.exist;
expect(keys).to.equal(storedKeys);
}
});
});
describe('onConnect', function() {
var credentials = {
imap: {},
smtp: {}
};
beforeEach(function() {
emailStub._account = {};
sinon.stub(account, 'isOnline').returns(true);
});
afterEach(function() {
account.isOnline.restore();
});
it('should fail due to _auth.getCredentials', function() {
authStub.getCredentials.yields(new Error('asdf'));
account.onConnect();
expect(dialogStub.error.calledOnce).to.be.true;
});
it('should work', function() {
authStub.getCredentials.yields(null, credentials);
emailStub.onConnect.yields();
account.onConnect();
expect(emailStub.onConnect.calledOnce).to.be.true;
expect(dialogStub.error.calledOnce).to.be.true;
});
});
describe('onDisconnect', function() {
it('should work', function() {
account.onDisconnect();
expect(emailStub.onDisconnect.calledOnce).to.be.true;
});
});
describe('logout', function() {
it('should fail due to _auth.logout', function() {
authStub.logout.yields(new Error());
account.logout();
expect(dialogStub.error.calledOnce).to.be.true;
});
it('should fail due to _emailDao.onDisconnect', function() {
authStub.logout.yields();
emailStub.onDisconnect.yields(new Error());
account.logout();
expect(dialogStub.error.calledOnce).to.be.true;
});
});
});

View File

@ -4,11 +4,12 @@ var mailreader = require('mailreader'),
ImapClient = require('imap-client'),
PgpMailer = require('pgpmailer'),
PgpBuilder = require('pgpbuilder'),
cfg = require('../../src/js/app-config').config,
EmailDAO = require('../../src/js/dao/email-dao'),
KeychainDAO = require('../../src/js/dao/keychain-dao'),
PGP = require('../../src/js/crypto/pgp'),
DeviceStorageDAO = require('../../src/js/dao/devicestorage-dao');
cfg = require('../../../src/js/app-config').config,
EmailDAO = require('../../../src/js/email/email'),
KeychainDAO = require('../../../src/js/service/keychain'),
PGP = require('../../../src/js/crypto/pgp'),
DeviceStorageDAO = require('../../../src/js/service/devicestorage'),
Dialog = require('../../../src/js/util/dialog');
describe('Email DAO unit tests', function() {
@ -19,7 +20,7 @@ describe('Email DAO unit tests', function() {
var dao;
// mocks
var keychainStub, imapClientStub, pgpMailerStub, pgpBuilderStub, pgpStub, devicestorageStub, parseStub;
var keychainStub, imapClientStub, pgpMailerStub, pgpBuilderStub, pgpStub, devicestorageStub, parseStub, dialogStub;
// config
var emailAddress, passphrase, asymKeySize, account;
@ -116,11 +117,12 @@ describe('Email DAO unit tests', function() {
pgpStub = sinon.createStubInstance(PGP);
parseStub = sinon.stub(mailreader, 'parse');
devicestorageStub = sinon.createStubInstance(DeviceStorageDAO);
dialogStub = sinon.createStubInstance(Dialog);
//
// setup the SUT
//
dao = new EmailDAO(keychainStub, pgpStub, devicestorageStub, pgpBuilderStub, mailreader);
dao = new EmailDAO(keychainStub, pgpStub, devicestorageStub, pgpBuilderStub, mailreader, dialogStub);
dao._account = account;
dao._pgpMailer = pgpMailerStub;
dao._imapClient = imapClientStub;
@ -1897,64 +1899,55 @@ describe('Email DAO unit tests', function() {
setFlagsStub = sinon.stub(dao, 'setFlags');
});
it('should get new message', function(done) {
it('should get new message', function() {
fetchMessagesStub.withArgs({
folder: inboxFolder,
firstUid: 1,
lastUid: 3
}).yieldsAsync();
dao.onError = function(err) {
expect(err).to.not.exist;
expect(fetchMessagesStub.calledOnce).to.be.true;
done();
};
}).yields();
dao._onSyncUpdate({
type: 'new',
path: inboxFolder.path,
list: [1, 3]
});
expect(dialogStub.error.calledOnce).to.be.true;
expect(fetchMessagesStub.calledOnce).to.be.true;
});
it('should delete message', function(done) {
it('should delete message', function() {
deleteMessagesStub.withArgs({
folder: inboxFolder,
message: msgs[0],
localOnly: true
}).yieldsAsync();
dao.onError = function(err) {
expect(err).to.not.exist;
expect(deleteMessagesStub.calledOnce).to.be.true;
done();
};
}).yields();
dao._onSyncUpdate({
type: 'deleted',
path: inboxFolder.path,
list: [5]
});
expect(dialogStub.error.calledOnce).to.be.true;
expect(deleteMessagesStub.calledOnce).to.be.true;
});
it('should fetch flags', function(done) {
it('should fetch flags', function() {
setFlagsStub.withArgs({
folder: inboxFolder,
message: msgs[0],
localOnly: true
}).yieldsAsync();
dao.onError = function(err) {
expect(err).to.not.exist;
expect(setFlagsStub.calledOnce).to.be.true;
done();
};
}).yields();
dao._onSyncUpdate({
type: 'messages',
path: inboxFolder.path,
list: msgs
});
expect(dialogStub.error.calledOnce).to.be.true;
expect(setFlagsStub.calledOnce).to.be.true;
});
});
});

View File

@ -1,9 +1,9 @@
'use strict';
var OutboxBO = require('../../src/js/bo/outbox'),
KeychainDAO = require('../../src/js/dao/keychain-dao'),
EmailDAO = require('../../src/js/dao/email-dao'),
DeviceStorageDAO = require('../../src/js/dao/devicestorage-dao');
var OutboxBO = require('../../../src/js/email/outbox'),
KeychainDAO = require('../../../src/js/service/keychain'),
EmailDAO = require('../../../src/js/email/email'),
DeviceStorageDAO = require('../../../src/js/service/devicestorage');
describe('Outbox Business Object unit test', function() {
var outbox, emailDaoStub, devicestorageStub, keychainStub,

View File

@ -0,0 +1,96 @@
'use strict';
describe('Search Service unit test', function() {
var search;
beforeEach(function() {
angular.module('search-test', ['woEmail']);
angular.mock.module('search-test');
angular.mock.inject(function($injector) {
search = $injector.get('search');
});
});
afterEach(function() {});
describe('filter', function() {
var message1 = {
to: [{
name: 'name1',
address: 'address1'
}],
subject: 'subject1',
body: 'body1',
html: 'html1'
},
message2 = {
to: [{
name: 'name2',
address: 'address2'
}],
subject: 'subject2',
body: 'body2',
html: 'html2'
},
message3 = {
to: [{
name: 'name3',
address: 'address3'
}],
subject: 'subject3',
body: 'body1',
html: 'html1',
encrypted: true
},
message4 = {
to: [{
name: 'name4',
address: 'address4'
}],
subject: 'subject4',
body: 'body1',
html: 'html1',
encrypted: true,
decrypted: true
},
testMessages = [message1, message2, message3, message4];
it('return same messages array on empty query string', function() {
var result = search.filter(testMessages, '');
expect(result).to.equal(testMessages);
});
it('return message1 on matching subject', function() {
var result = search.filter(testMessages, 'subject1');
expect(result.length).to.equal(1);
expect(result[0]).to.equal(message1);
});
it('return message1 on matching name', function() {
var result = search.filter(testMessages, 'name1');
expect(result.length).to.equal(1);
expect(result[0]).to.equal(message1);
});
it('return message1 on matching address', function() {
var result = search.filter(testMessages, 'address1');
expect(result.length).to.equal(1);
expect(result[0]).to.equal(message1);
});
it('return plaintext and decrypted messages on matching body', function() {
var result = search.filter(testMessages, 'body1');
expect(result.length).to.equal(2);
expect(result[0]).to.equal(message1);
expect(result[1]).to.equal(message4);
});
it('return plaintext and decrypted messages on matching html', function() {
var result = search.filter(testMessages, 'html1');
expect(result.length).to.equal(2);
expect(result[0]).to.equal(message1);
expect(result[1]).to.equal(message4);
});
});
});

View File

@ -1,236 +0,0 @@
'use strict';
var mocks = angular.mock,
LoginCtrl = require('../../src/js/controller/login'),
EmailDAO = require('../../src/js/dao/email-dao'),
Auth = require('../../src/js/bo/auth'),
appController = require('../../src/js/app-controller'),
KeychainDAO = require('../../src/js/dao/keychain-dao');
describe('Login Controller unit test', function() {
var scope, location, ctrl,
origEmailDao, emailDaoMock,
origKeychain, keychainMock,
origAuth, authStub,
emailAddress = 'fred@foo.com',
startAppStub,
checkForUpdateStub,
initStub;
describe('initialization', function() {
var hasChrome, hasIdentity;
beforeEach(function() {
hasChrome = !!window.chrome;
hasIdentity = !!window.chrome.identity;
window.chrome = window.chrome || {};
window.chrome.identity = window.chrome.identity || {};
// remember original module to restore later, then replace it
origEmailDao = appController._emailDao;
origKeychain = appController._keychain;
origAuth = appController._auth;
appController._emailDao = emailDaoMock = sinon.createStubInstance(EmailDAO);
appController._keychain = keychainMock = sinon.createStubInstance(KeychainDAO);
appController._auth = authStub = sinon.createStubInstance(Auth);
startAppStub = sinon.stub(appController, 'start');
checkForUpdateStub = sinon.stub(appController, 'checkForUpdate');
initStub = sinon.stub(appController, 'init');
});
afterEach(function() {
// restore the browser
if (!hasIdentity) {
delete window.chrome.identity;
}
if (!hasChrome) {
delete window.chrome;
}
// restore the app controller module
appController._emailDao = origEmailDao;
appController._keychain = origKeychain;
appController._auth = origAuth;
appController.start.restore && appController.start.restore();
appController.checkForUpdate.restore && appController.checkForUpdate.restore();
appController.init.restore && appController.init.restore();
location.path.restore && location.path.restore();
startAppStub.restore();
checkForUpdateStub.restore();
initStub.restore();
});
it('should forward directly to desktop for empty passphrase', function(done) {
var testKeys = {
privateKey: 'a',
publicKey: 'b'
};
startAppStub.yields();
authStub.getEmailAddress.yields(null, {
emailAddress: emailAddress,
realname: 'asd'
});
authStub.storeCredentials.yields();
initStub.yields(null, testKeys);
emailDaoMock.unlock.withArgs({
keypair: testKeys,
passphrase: undefined
}).yields();
angular.module('logintest', []);
mocks.module('logintest');
mocks.inject(function($controller, $rootScope, $location) {
location = $location;
sinon.stub(location, 'path', function(path) {
expect(path).to.equal('/desktop');
expect(startAppStub.calledOnce).to.be.true;
expect(checkForUpdateStub.calledOnce).to.be.true;
expect(authStub.getEmailAddress.calledOnce).to.be.true;
expect(authStub.storeCredentials.calledOnce).to.be.true;
done();
});
scope = $rootScope.$new();
scope.state = {};
ctrl = $controller(LoginCtrl, {
$location: location,
$scope: scope
});
});
});
it('should forward to existing user login', function(done) {
var testKeys = {
privateKey: 'a',
publicKey: 'b'
};
startAppStub.yields();
authStub.getEmailAddress.yields(null, {
emailAddress: emailAddress,
realname: 'asd'
});
initStub.yields(null, testKeys);
emailDaoMock.unlock.withArgs({
keypair: testKeys,
passphrase: undefined
}).yields({});
angular.module('logintest', []);
mocks.module('logintest');
mocks.inject(function($controller, $rootScope, $location) {
location = $location;
sinon.stub(location, 'path', function(path) {
expect(path).to.equal('/login-existing');
expect(startAppStub.calledOnce).to.be.true;
expect(checkForUpdateStub.calledOnce).to.be.true;
expect(authStub.getEmailAddress.calledOnce).to.be.true;
done();
});
scope = $rootScope.$new();
scope.state = {};
ctrl = $controller(LoginCtrl, {
$location: location,
$scope: scope
});
});
});
it('should forward to privatekey download login', function(done) {
startAppStub.yields();
authStub.getEmailAddress.yields(null, {
emailAddress: emailAddress,
realname: 'asd'
});
initStub.yields(null, {
publicKey: 'b'
});
keychainMock.requestPrivateKeyDownload.yields(null, {});
angular.module('logintest', []);
mocks.module('logintest');
mocks.inject(function($controller, $rootScope, $location) {
location = $location;
sinon.stub(location, 'path', function(path) {
expect(path).to.equal('/login-privatekey-download');
expect(startAppStub.calledOnce).to.be.true;
expect(checkForUpdateStub.calledOnce).to.be.true;
expect(authStub.getEmailAddress.calledOnce).to.be.true;
expect(keychainMock.requestPrivateKeyDownload.calledOnce).to.be.true;
done();
});
scope = $rootScope.$new();
scope.state = {};
ctrl = $controller(LoginCtrl, {
$location: location,
$scope: scope
});
});
});
it('should forward to new device login', function(done) {
startAppStub.yields();
authStub.getEmailAddress.yields(null, {
emailAddress: emailAddress,
realname: 'asd'
});
initStub.yields(null, {
publicKey: 'b'
});
keychainMock.requestPrivateKeyDownload.yields();
angular.module('logintest', []);
mocks.module('logintest');
mocks.inject(function($controller, $rootScope, $location) {
location = $location;
sinon.stub(location, 'path', function(path) {
expect(path).to.equal('/login-new-device');
expect(startAppStub.calledOnce).to.be.true;
expect(checkForUpdateStub.calledOnce).to.be.true;
expect(authStub.getEmailAddress.calledOnce).to.be.true;
expect(keychainMock.requestPrivateKeyDownload.calledOnce).to.be.true;
done();
});
scope = $rootScope.$new();
scope.state = {};
ctrl = $controller(LoginCtrl, {
$location: location,
$scope: scope
});
});
});
it('should forward to initial login', function(done) {
startAppStub.yields();
authStub.getEmailAddress.yields(null, {
emailAddress: emailAddress,
realname: 'asd'
});
initStub.yields();
angular.module('logintest', []);
mocks.module('logintest');
mocks.inject(function($controller, $rootScope, $location) {
location = $location;
sinon.stub(location, 'path', function(path) {
expect(path).to.equal('/login-initial');
expect(startAppStub.calledOnce).to.be.true;
expect(checkForUpdateStub.calledOnce).to.be.true;
expect(authStub.getEmailAddress.calledOnce).to.be.true;
done();
});
scope = $rootScope.$new();
scope.state = {};
ctrl = $controller(LoginCtrl, {
$location: location,
$scope: scope
});
});
});
});
});

View File

@ -1,7 +1,8 @@
'use strict';
var RestDAO = require('../../src/js/dao/rest-dao'),
AdminDAO = require('../../src/js/dao/admin-dao');
var RestDAO = require('../../../src/js/service/rest'),
AdminDAO = require('../../../src/js/service/admin'),
appConfig = require('../../../src/js/app-config');
describe('Admin DAO unit tests', function() {
@ -11,7 +12,7 @@ describe('Admin DAO unit tests', function() {
beforeEach(function() {
restDaoStub = sinon.createStubInstance(RestDAO);
adminDao = new AdminDAO(restDaoStub);
adminDao = new AdminDAO(restDaoStub, appConfig);
});
afterEach(function() {});

View File

@ -1,9 +1,9 @@
'use strict';
var Auth = require('../../src/js/bo/auth'),
OAuth = require('../../src/js/util/oauth'),
PGP = require('../../src/js/crypto/pgp'),
DeviceStorageDAO = require('../../src/js/dao/devicestorage-dao');
var Auth = require('../../../src/js/service/auth'),
OAuth = require('../../../src/js/service/oauth'),
PGP = require('../../../src/js/crypto/pgp'),
DeviceStorageDAO = require('../../../src/js/service/devicestorage');
describe('Auth unit tests', function() {
// Constancts

View File

@ -1,7 +1,7 @@
'use strict';
var LawnchairDAO = require('../../src/js/dao/lawnchair-dao'),
DeviceStorageDAO = require('../../src/js/dao/devicestorage-dao');
var LawnchairDAO = require('../../../src/js/service/lawnchair'),
DeviceStorageDAO = require('../../../src/js/service/devicestorage');
var testUser = 'test@example.com';
@ -17,14 +17,9 @@ describe('Device Storage DAO unit tests', function() {
afterEach(function() {});
describe('init', function() {
it('should work', function(done) {
lawnchairDaoStub.init.yields();
storageDao.init(testUser, function(err) {
expect(err).to.not.exist;
expect(lawnchairDaoStub.init.calledOnce).to.be.true;
done();
});
it('should work', function() {
storageDao.init(testUser);
expect(lawnchairDaoStub.init.calledOnce).to.be.true;
});
});

View File

@ -1,7 +1,8 @@
'use strict';
var RestDAO = require('../../src/js/dao/rest-dao'),
InvitationDAO = require('../../src/js/dao/invitation-dao');
var RestDAO = require('../../../src/js/service/rest'),
InvitationDAO = require('../../../src/js/service/invitation'),
appConfig = require('../../../src/js/app-config');
describe('Invitation DAO unit tests', function() {
var restDaoStub, invitationDao,
@ -11,7 +12,7 @@ describe('Invitation DAO unit tests', function() {
beforeEach(function() {
restDaoStub = sinon.createStubInstance(RestDAO);
invitationDao = new InvitationDAO(restDaoStub);
invitationDao = new InvitationDAO(restDaoStub, appConfig);
});
describe('initialization', function() {

View File

@ -1,17 +1,19 @@
'use strict';
var LawnchairDAO = require('../../src/js/dao/lawnchair-dao'),
PublicKeyDAO = require('../../src/js/dao/publickey-dao'),
KeychainDAO = require('../../src/js/dao/keychain-dao'),
PrivateKeyDAO = require('../../src/js/dao/privatekey-dao'),
Crypto = require('../../src/js/crypto/crypto'),
PGP = require('../../src/js/crypto/pgp');
var LawnchairDAO = require('../../../src/js/service/lawnchair'),
PublicKeyDAO = require('../../../src/js/service/publickey'),
KeychainDAO = require('../../../src/js/service/keychain'),
PrivateKeyDAO = require('../../../src/js/service/privatekey'),
Crypto = require('../../../src/js/crypto/crypto'),
PGP = require('../../../src/js/crypto/pgp'),
Dialog = require('../../../src/js/util/dialog'),
appConfig = require('../../../src/js/app-config');
var testUser = 'test@example.com';
describe('Keychain DAO unit tests', function() {
var keychainDao, lawnchairDaoStub, pubkeyDaoStub, privkeyDaoStub, cryptoStub, pgpStub;
var keychainDao, lawnchairDaoStub, pubkeyDaoStub, privkeyDaoStub, cryptoStub, pgpStub, dialogStub;
beforeEach(function() {
lawnchairDaoStub = sinon.createStubInstance(LawnchairDAO);
@ -19,11 +21,25 @@ describe('Keychain DAO unit tests', function() {
privkeyDaoStub = sinon.createStubInstance(PrivateKeyDAO);
cryptoStub = sinon.createStubInstance(Crypto);
pgpStub = sinon.createStubInstance(PGP);
keychainDao = new KeychainDAO(lawnchairDaoStub, pubkeyDaoStub, privkeyDaoStub, cryptoStub, pgpStub);
dialogStub = sinon.createStubInstance(Dialog);
keychainDao = new KeychainDAO(lawnchairDaoStub, pubkeyDaoStub, privkeyDaoStub, cryptoStub, pgpStub, dialogStub, appConfig);
});
afterEach(function() {});
describe('requestPermissionForKeyUpdate', function() {
it('should work', function() {
var opt = {
newKey: {},
userId: 'asdf@example.com'
};
keychainDao.requestPermissionForKeyUpdate(opt, function() {
expect(dialogStub.confirm.calledOnce).to.be.true;
});
});
});
describe('verify public key', function() {
it('should verify public key', function(done) {
var uuid = 'asdfasdfasdfasdf';

Some files were not shown because too many files have changed in this diff Show More