Merge pull request #141 from whiteout-io/dev/WO-567

Dev/wo 567
This commit is contained in:
Tankred Hase 2014-10-16 12:12:07 +02:00
commit 5d09e32320
100 changed files with 19058 additions and 19615 deletions

6
.gitignore vendored
View File

@ -7,7 +7,7 @@ src/css/
dist/
release/
test/integration/src/
src/lib/*.js
src/js/crypto/aes-gcm.js
src/js/crypto/util.js
.elasticbeanstalk/
test/*/index.js
**/*.browserified.js
**/*.js.map

View File

@ -18,31 +18,29 @@
"unused": true,
"predef": [
"self",
"importScripts",
"console",
"Notification",
"importScripts",
"process",
"Event",
"QUnit",
"test",
"asyncTest",
"ok",
"equal",
"deepEqual",
"start",
"process",
"chrome",
"requirejs",
"define",
"self",
"Notification",
"Event",
"sinon",
"mocha",
"chai",
"expect",
"describe",
"it",
"chai",
"sinon",
"mocha",
"before",
"beforeEach",
"after",
"afterEach"
"afterEach",
"FastClick",
"angular",
"forge",
"Lawnchair",
"_",
"openpgp"
],
"globals": {

View File

@ -1,11 +1,22 @@
module.exports = function(grunt) {
'use strict';
require('time-grunt')(grunt);
var version = grunt.option('release'),
zipName = (version) ? version : 'DEV';
var browserifyOpt = {
exclude: ['openpgp', 'node-forge', 'net', 'tls', 'crypto'], // node apis not required at build time
ignore: ['buffer'], // node apis to be stubbed for runtime
browserifyOptions: {
debug: true
}
};
// Project configuration.
grunt.initConfig({
connect: {
dev: {
options: {
@ -23,32 +34,27 @@ module.exports = function(grunt) {
},
jshint: {
all: ['Gruntfile.js', 'src/*.js', 'src/js/**/*.js', 'test/unit/*.js', 'test/integration/*.js'],
all: ['Gruntfile.js', 'src/*.js', 'src/js/**/*.js', 'test/unit/*-test.js', 'test/integration/*-test.js'],
options: {
jshintrc: '.jshintrc'
}
},
mocha: {
mocha_phantomjs: {
all: {
options: {
urls: [
'http://localhost:<%= connect.test.options.port %>/test/unit/index.html',
'http://localhost:<%= connect.test.options.port %>/test/integration/index.html'
],
run: false,
reporter: 'Spec',
log: false,
// phanotmjs is soooo slow
timeout: 100000
]
}
}
},
clean: {
dist: ['dist', 'src/lib/*.js', 'test/lib', 'test/integration/src']
dist: ['dist', 'test/lib', 'test/integration/src']
},
sass: {
dist: {
files: {
@ -57,6 +63,7 @@ module.exports = function(grunt) {
}
}
},
autoprefixer: {
options: {
browsers: ['last 2 versions']
@ -68,9 +75,10 @@ module.exports = function(grunt) {
}
}
},
csso: {
options: {
banner: '/*! Copyright © 2013, Whiteout Networks GmbH. All rights reserved.*/\n'
banner: '/*! Copyright © <%= grunt.template.today("yyyy") %>, Whiteout Networks GmbH.*/\n'
},
dist: {
files: {
@ -79,6 +87,7 @@ module.exports = function(grunt) {
}
}
},
watch: {
css: {
files: ['src/sass/**/*.scss'],
@ -86,7 +95,7 @@ module.exports = function(grunt) {
},
js: {
files: ['src/js/**/*.js'],
tasks: ['copy:js', 'copy:integration', 'manifest']
tasks: ['dist-js', 'copy:integration', 'manifest']
},
lib: {
files: ['src/lib/**/*.js'],
@ -97,59 +106,234 @@ module.exports = function(grunt) {
tasks: ['copy:app', 'copy:ca', 'copy:tpl', 'copy:img', 'copy:font', 'manifest-dev', 'manifest']
}
},
copy: {
npm: {
expand: true,
flatten: true,
cwd: 'node_modules/',
src: [
'requirejs/require.js',
'imap-client/src/*.js',
'imap-client/node_modules/browserbox/src/*.js',
'imap-client/node_modules/browserbox/node_modules/wo-imap-handler/src/*.js',
'imap-client/node_modules/browserbox/node_modules/mimefuncs/src/*.js',
'imap-client/node_modules/browserbox/node_modules/tcp-socket/src/*.js',
'imap-client/node_modules/browserbox/node_modules/wo-utf7/src/*.js',
'mailreader/src/*.js',
'mailreader/node_modules/mimeparser/src/*.js',
'mailreader/node_modules/mimeparser/node_modules/wo-addressparser/src/*.js',
'pgpbuilder/src/*.js',
'pgpbuilder/node_modules/mailbuild/src/*.js',
'pgpbuilder/node_modules/mailbuild/node_modules/mimetypes/src/*.js',
'pgpbuilder/node_modules/mailbuild/node_modules/punycode/punycode.min.js',
'pgpmailer/src/*.js',
'pgpmailer/node_modules/wo-smtpclient/src/*.js',
'pgpmailer/node_modules/wo-smtpclient/node_modules/wo-stringencoding/dist/stringencoding.js',
'axe-logger/axe.js',
'dompurify/purify.js',
'jquery/dist/jquery.min.js',
'ng-infinite-scroll/build/ng-infinite-scroll.min.js'
],
dest: 'src/lib/'
browserify: {
app: {
files: {
'dist/js/app.browserified.js': ['src/js/app.js']
},
options: browserifyOpt
},
pbkdf2Worker: {
files: {
'dist/js/pbkdf2-worker.browserified.js': ['src/js/crypto/pbkdf2-worker.js']
},
options: browserifyOpt
},
mailreaderWorker: {
files: {
'dist/js/mailreader-parser-worker.browserified.js': ['node_modules/mailreader/src/mailreader-parser-worker-browserify.js']
},
options: browserifyOpt
},
tlsWorker: {
files: {
'dist/js/tcp-socket-tls-worker.browserified.js': ['node_modules/tcp-socket/src/tcp-socket-tls-worker.js']
},
options: browserifyOpt
},
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/dialog-ctrl-test.js',
'test/unit/add-account-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/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'
]
},
options: browserifyOpt
},
integrationTest: {
files: {
'test/integration/index.browserified.js': [
'test/integration/email-dao-test.js',
'test/main.js'
]
},
options: browserifyOpt
}
},
exorcise: {
app: {
files: {
'dist/js/app.browserified.js.map': ['dist/js/app.browserified.js'],
}
},
unitTest: {
files: {
'test/unit/index.browserified.js.map': ['test/unit/index.browserified.js'],
}
},
integrationTest: {
files: {
'test/integration/index.browserified.js.map': ['test/integration/index.browserified.js'],
}
}
},
uglify: {
app: {
files: {
'dist/js/app.min.js': [
'src/lib/underscore/underscore-min.js',
'node_modules/jquery/dist/jquery.min.js',
'src/lib/angular/angular.min.js',
'src/lib/angular/angular-route.min.js',
'src/lib/angular/angular-animate.min.js',
'src/lib/ngtagsinput/ng-tags-input.min.js',
'node_modules/ng-infinite-scroll/build/ng-infinite-scroll.min.js',
'src/lib/fastclick/fastclick.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',
'dist/js/app.browserified.js'
]
},
options: {
mangle: false,
sourceMap: true,
sourceMapIn: 'dist/js/app.browserified.js.map',
sourceMapIncludeSources: true,
sourceMapName: 'dist/js/app.min.js.map'
}
},
readSandbox: {
files: {
'dist/js/read-sandbox.min.js': [
'node_modules/dompurify/purify.js',
'src/js/controller/read-sandbox.js'
]
},
options: {
sourceMap: true,
sourceMapName: 'dist/js/read-sandbox.min.js.map'
}
},
pbkdf2Worker: {
files: {
'dist/js/pbkdf2-worker.min.js': ['dist/js/pbkdf2-worker.browserified.js']
}
},
mailreaderWorker: {
files: {
'dist/js/mailreader-parser-worker.min.js': ['dist/js/mailreader-parser-worker.browserified.js']
},
options: {
sourceMap: true,
sourceMapName: 'dist/js/mailreader-parser-worker.min.js.map'
}
},
tlsWorker: {
files: {
'dist/js/tcp-socket-tls-worker.min.js': ['dist/js/tcp-socket-tls-worker.browserified.js']
},
options: {
sourceMap: true,
sourceMapName: 'dist/js/tcp-socket-tls-worker.min.js.map'
}
},
unitTest: {
files: {
'test/unit/index.js': [
'src/lib/underscore/underscore-min.js',
'node_modules/jquery/dist/jquery.min.js',
'src/lib/angular/angular.min.js',
'src/lib/angular/angular-route.min.js',
'src/lib/angular/angular-animate.min.js',
'node_modules/angularjs/src/ngMock/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',
'test/unit/index.browserified.js'
]
},
options: {
mangle: false,
sourceMap: true,
sourceMapIn: 'test/unit/index.browserified.js.map',
sourceMapIncludeSources: true,
sourceMapName: 'test/unit/index.js.map'
}
},
integrationTest: {
files: {
'test/integration/index.js': [
'src/lib/underscore/underscore-min.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',
'test/integration/index.browserified.js'
]
},
options: {
mangle: false,
sourceMap: true,
sourceMapIn: 'test/integration/index.browserified.js.map',
sourceMapIncludeSources: true,
sourceMapName: 'test/integration/index.js.map'
}
},
options: {
banner: '/*! Copyright © <%= grunt.template.today("yyyy") %>, Whiteout Networks GmbH.*/\n'
}
},
copy: {
npmDev: {
expand: true,
flatten: true,
cwd: 'node_modules/',
src: ['requirejs/require.js', 'mocha/mocha.css', 'mocha/mocha.js', 'chai/chai.js', 'sinon/pkg/sinon.js', 'angularjs/src/ngMock/angular-mocks.js', 'browsercrow/src/*.js', 'browsersmtp/src/*.js'],
cwd: './',
src: [
'node_modules/mocha/mocha.css',
'node_modules/mocha/mocha.js',
'node_modules/chai/chai.js',
'node_modules/sinon/pkg/sinon.js',
'node_modules/browsercrow/src/*.js',
'node_modules/browsersmtp/src/*.js',
'src/lib/openpgp/openpgp.js',
'src/lib/openpgp/openpgp.worker.js',
'src/lib/forge/forge.min.js',
'dist/js/pbkdf2-worker.min.js'
],
dest: 'test/lib/'
},
cryptoLib: {
expand: true,
cwd: 'node_modules/crypto-lib/src/',
src: ['*.js'],
dest: 'src/js/crypto/'
},
lib: {
expand: true,
flatten: true,
cwd: 'src/lib/',
src: ['**'],
dest: 'dist/lib/'
},
js: {
expand: true,
cwd: 'src/js/',
src: ['**'],
src: ['openpgp/openpgp.js', 'openpgp/openpgp.worker.js', 'forge/forge.min.js'],
dest: 'dist/js/'
},
font: {
@ -170,23 +354,11 @@ module.exports = function(grunt) {
src: ['*'],
dest: 'dist/tpl/'
},
ca: {
expand: true,
cwd: 'src/ca/',
src: ['*'],
dest: 'dist/ca/'
},
app: {
expand: true,
cwd: 'src/',
src: ['*.html', '*.js', '*.json', 'manifest.*'],
dest: 'dist/'
},
integration: {
expand: true,
cwd: 'src/',
src: ['**'],
dest: 'test/integration/src/'
}
},
@ -200,16 +372,6 @@ module.exports = function(grunt) {
cwd: 'dist/',
src: ['**/*'],
dest: 'release/'
},
nodeWebkit: {
options: {
mode: 'zip',
archive: 'release/whiteout-mail_' + zipName + '.nw'
},
expand: true,
cwd: 'dist/',
src: ['**/*'],
dest: '/'
}
},
@ -220,31 +382,30 @@ module.exports = function(grunt) {
timestamp: true,
hash: true,
cache: ['socket.io/socket.io.js'],
exclude: ['appcache.manifest', 'manifest.webapp'],
exclude: [
'appcache.manifest',
'manifest.webapp',
'js/app.min.js.map',
'js/app.browserified.js',
'js/app.browserified.js.map',
'js/crypto/pbkdf2-worker.browserified.js',
'js/pbkdf2-worker.browserified.js',
'js/read-sandbox.min.js.map'
],
master: ['index.html']
},
src: ['**/*.*'],
dest: 'dist/appcache.manifest'
}
},
}
nodewebkit: {
options: {
version: '0.9.2', // node-webkit version
build_dir: './release/node-webkit/', // Where the build version of my node-webkit app is saved
mac: true, // We want to build it for mac
win: false, // We want to build it for win
linux32: false, // We don't need linux32
linux64: false, // We don't need linux64
},
src: ['./dist/**/*'] // Your node-webkit app
},
});
// Load the plugin(s)
grunt.loadNpmTasks('grunt-browserify');
grunt.loadNpmTasks('grunt-contrib-uglify');
grunt.loadNpmTasks('grunt-contrib-connect');
grunt.loadNpmTasks('grunt-contrib-jshint');
grunt.loadNpmTasks('grunt-mocha');
grunt.loadNpmTasks('grunt-contrib-clean');
grunt.loadNpmTasks('grunt-csso');
grunt.loadNpmTasks('grunt-contrib-sass');
@ -252,18 +413,19 @@ module.exports = function(grunt) {
grunt.loadNpmTasks('grunt-contrib-watch');
grunt.loadNpmTasks('grunt-contrib-copy');
grunt.loadNpmTasks('grunt-contrib-compress');
grunt.loadNpmTasks('grunt-node-webkit-builder');
grunt.loadNpmTasks('grunt-manifest');
grunt.loadNpmTasks('grunt-mocha-phantomjs');
grunt.loadNpmTasks('grunt-exorcise');
// Build tasks
grunt.registerTask('dist-npm', ['copy:npm', 'copy:npmDev', 'copy:cryptoLib']);
grunt.registerTask('dist-css', ['sass', 'autoprefixer', 'csso']);
grunt.registerTask('dist-js', ['browserify', 'exorcise', 'uglify']);
grunt.registerTask('dist-copy', ['copy']);
grunt.registerTask('dist', ['clean', 'dist-npm', 'dist-css', 'dist-copy', 'manifest']);
grunt.registerTask('dist', ['clean', 'dist-css', 'dist-js', 'dist-copy', 'manifest']);
// Test/Dev tasks
grunt.registerTask('dev', ['connect:dev']);
grunt.registerTask('test', ['jshint', 'connect:test', 'mocha']);
grunt.registerTask('test', ['jshint', 'connect:test', 'mocha_phantomjs']);
grunt.registerTask('prod', ['connect:prod']);
//
@ -335,4 +497,4 @@ module.exports = function(grunt) {
grunt.registerTask('release-stable', ['dist', 'manifest-stable', 'compress']);
grunt.registerTask('default', ['release-dev']);
};
};

View File

@ -24,25 +24,27 @@
},
"scripts": {
"test": "grunt && grunt test",
"start": "node server.js"
"start": "node server.js",
"postinstall": "dir=$(pwd) && cd node_modules/mailreader/ && npm install --production && cd $dir"
},
"dependencies": {
"axe-logger": "~0.0.2",
"compression": "^1.0.11",
"config": "^1.0.2",
"crypto-lib": "~0.2.1",
"dompurify": "~0.4.2",
"express": "^4.8.3",
"imap-client": "~0.4.3",
"jquery": "~2.1.1",
"mailreader": "~0.3.5",
"mailreader": "~0.4.0",
"morgan": "^1.2.3",
"ng-infinite-scroll": "~1.1.2",
"npmlog": "^0.1.1",
"pgpbuilder": "~0.4.0",
"pgpmailer": "~0.4.0",
"requirejs": "~2.1.14",
"config": "^1.0.2",
"morgan": "^1.2.3",
"npmlog": "^0.1.1",
"socket.io": "^1.0.6"
"socket.io": "^1.0.6",
"tcp-socket": "~0.3.13",
"wo-smtpclient": "^0.3.8"
},
"devDependencies": {
"angularjs": "https://github.com/whiteout-io/angular.js/tarball/npm-version",
@ -50,19 +52,22 @@
"browsersmtp": "https://github.com/whiteout-io/browsersmtp/tarball/master",
"chai": "~1.7.2",
"grunt": "~0.4.1",
"grunt-contrib-clean": "~0.5.0",
"grunt-contrib-copy": "~0.4.1",
"grunt-manifest": "^0.4.0",
"grunt-autoprefixer": "~0.7.2",
"grunt-browserify": "^3.0.1",
"grunt-contrib-clean": "~0.5.0",
"grunt-contrib-compress": "~0.5.2",
"grunt-contrib-connect": "~0.5.0",
"grunt-contrib-copy": "~0.4.1",
"grunt-contrib-jshint": "~0.6.4",
"grunt-contrib-sass": "~0.7.3",
"grunt-contrib-uglify": "^0.6.0",
"grunt-contrib-watch": "~0.5.3",
"grunt-csso": "~0.6.1",
"grunt-mocha": "~0.4.1",
"grunt-node-webkit-builder": "~0.1.17",
"mocha": "~1.13.0",
"sinon": "~1.7.3"
"grunt-exorcise": "^0.2.0",
"grunt-manifest": "^0.4.0",
"grunt-mocha-phantomjs": "^0.6.0",
"mocha": "^1.21.4",
"sinon": "~1.7.3",
"time-grunt": "^1.0.0"
}
}

View File

@ -26,9 +26,9 @@
<!-- The Scripts -->
<script src="socket.io/socket.io.js"></script>
<script src="lib/require.js"></script>
<script src="require-config.js"></script>
<script src="js/app.js"></script>
<script src="js/openpgp.js"></script>
<script src="js/forge.min.js"></script>
<script src="js/app.min.js"></script>
</head>
<body key-shortcuts>

View File

@ -1,223 +1,217 @@
define(function(require) {
'use strict';
'use strict';
var _ = require('underscore'),
app = {},
appVersion, cloudUrl, keychainUrl, clientId;
var appVersion, cloudUrl, keychainUrl, clientId;
// parse manifest to get configurations for current runtime
try {
var manifest = chrome.runtime.getManifest();
// get key server base url
cloudUrl = _.find(manifest.permissions, function(permission) {
return typeof permission === 'string' && permission.indexOf('https://keys') === 0;
});
// remove last '/' from url due to required syntax in manifest
cloudUrl = cloudUrl.substring(0, cloudUrl.length - 1);
// get keychain server base url
keychainUrl = _.find(manifest.permissions, function(permission) {
return typeof permission === 'string' && permission.indexOf('https://keychain') === 0;
});
// remove last '/' from url due to required syntax in manifest
keychainUrl = keychainUrl.substring(0, keychainUrl.length - 1);
// get client ID for OAuth requests
clientId = manifest.oauth2.client_id;
// get the app version
appVersion = manifest.version;
} catch (e) {}
// parse manifest to get configurations for current runtime
try {
var manifest = chrome.runtime.getManifest();
// get key server base url
cloudUrl = _.find(manifest.permissions, function(permission) {
return typeof permission === 'string' && permission.indexOf('https://keys') === 0;
});
// remove last '/' from url due to required syntax in manifest
cloudUrl = cloudUrl.substring(0, cloudUrl.length - 1);
// get keychain server base url
keychainUrl = _.find(manifest.permissions, function(permission) {
return typeof permission === 'string' && permission.indexOf('https://keychain') === 0;
});
// remove last '/' from url due to required syntax in manifest
keychainUrl = keychainUrl.substring(0, keychainUrl.length - 1);
// get client ID for OAuth requests
clientId = manifest.oauth2.client_id;
// get the app version
appVersion = manifest.version;
} catch (e) {}
/**
* Global app configurations
*/
app.config = {
cloudUrl: cloudUrl || 'https://keys.whiteout.io',
privkeyServerUrl: keychainUrl || 'https://keychain.whiteout.io',
adminUrl: 'https://admin-node.whiteout.io',
wmailDomain: 'wmail.io',
serverPrivateKeyId: 'EE342F0DDBB0F3BE',
symKeySize: 256,
symIvSize: 96,
asymKeySize: 2048,
workerPath: 'js',
reconnectInterval: 10000,
wmail: {
imap: {
host: 'imap.wmail.io',
port: 993,
secure: true,
ca: '-----BEGIN CERTIFICATE-----\r\nMIIGiTCCBXGgAwIBAgIDAn03MA0GCSqGSIb3DQEBBQUAMIGMMQswCQYDVQQGEwJJ\r\nTDEWMBQGA1UEChMNU3RhcnRDb20gTHRkLjErMCkGA1UECxMiU2VjdXJlIERpZ2l0\r\nYWwgQ2VydGlmaWNhdGUgU2lnbmluZzE4MDYGA1UEAxMvU3RhcnRDb20gQ2xhc3Mg\r\nMiBQcmltYXJ5IEludGVybWVkaWF0ZSBTZXJ2ZXIgQ0EwHhcNMTQwOTIxMTQ0ODQ0\r\nWhcNMTYwOTIxMTE0MzIxWjCBmTEZMBcGA1UEDRMQQ1NIeEdsdlg4Z3lRQ2c3TzEL\r\nMAkGA1UEBhMCREUxDzANBgNVBAgTBkJheWVybjEPMA0GA1UEBxMGTXVuaWNoMRUw\r\nEwYDVQQKEwxUYW5rcmVkIEhhc2UxEzARBgNVBAMUCioud21haWwuaW8xITAfBgkq\r\nhkiG9w0BCQEWEndlYm1hc3RlckB3bWFpbC5pbzCCASIwDQYJKoZIhvcNAQEBBQAD\r\nggEPADCCAQoCggEBAMkbMzFfZLYqfWG8yw2HvuNiFs5ajBFMrjG3fobePE674mPd\r\niFtXqbl3ydQ+umTtbJ6bztgxB3KgrL3lhp6IkD4VxB8YQJoYGhU6YH7FhP4QMm8l\r\ncnFLUZXEbcpcCg1tjL6+vvoTMUWEbV/zNtF/oiJ4AIOKwf0zUMZkTu1FCNrOrvpj\r\n6SAkOdBGzLTOAP5vxP43PfpZPZ4dLL2Be7ENYKXqPs0jSlUWpdT9l4AZG8rHKa2d\r\nccWvRoAsCfvpzGDoMYEx9+a1F1XjNZfzo7yJQcHdaE3Mj1eNqbjZe0+Vmact1S/3\r\nsubdcDVkFSfUMsAB5rH0D5mEhy7sMpt1WWCvOF8CAwEAAaOCAuMwggLfMAkGA1Ud\r\nEwQCMAAwCwYDVR0PBAQDAgOoMB0GA1UdJQQWMBQGCCsGAQUFBwMCBggrBgEFBQcD\r\nATAdBgNVHQ4EFgQUv+9x4PISm5Roq4pdNoaFJeIZFfUwHwYDVR0jBBgwFoAUEdsj\r\nRf1UzGpxb4SKA9e+9wEvJoYwHwYDVR0RBBgwFoIKKi53bWFpbC5pb4IId21haWwu\r\naW8wggFWBgNVHSAEggFNMIIBSTAIBgZngQwBAgIwggE7BgsrBgEEAYG1NwECAzCC\r\nASowLgYIKwYBBQUHAgEWImh0dHA6Ly93d3cuc3RhcnRzc2wuY29tL3BvbGljeS5w\r\nZGYwgfcGCCsGAQUFBwICMIHqMCcWIFN0YXJ0Q29tIENlcnRpZmljYXRpb24gQXV0\r\naG9yaXR5MAMCAQEagb5UaGlzIGNlcnRpZmljYXRlIHdhcyBpc3N1ZWQgYWNjb3Jk\r\naW5nIHRvIHRoZSBDbGFzcyAyIFZhbGlkYXRpb24gcmVxdWlyZW1lbnRzIG9mIHRo\r\nZSBTdGFydENvbSBDQSBwb2xpY3ksIHJlbGlhbmNlIG9ubHkgZm9yIHRoZSBpbnRl\r\nbmRlZCBwdXJwb3NlIGluIGNvbXBsaWFuY2Ugb2YgdGhlIHJlbHlpbmcgcGFydHkg\r\nb2JsaWdhdGlvbnMuMDUGA1UdHwQuMCwwKqAooCaGJGh0dHA6Ly9jcmwuc3RhcnRz\r\nc2wuY29tL2NydDItY3JsLmNybDCBjgYIKwYBBQUHAQEEgYEwfzA5BggrBgEFBQcw\r\nAYYtaHR0cDovL29jc3Auc3RhcnRzc2wuY29tL3N1Yi9jbGFzczIvc2VydmVyL2Nh\r\nMEIGCCsGAQUFBzAChjZodHRwOi8vYWlhLnN0YXJ0c3NsLmNvbS9jZXJ0cy9zdWIu\r\nY2xhc3MyLnNlcnZlci5jYS5jcnQwIwYDVR0SBBwwGoYYaHR0cDovL3d3dy5zdGFy\r\ndHNzbC5jb20vMA0GCSqGSIb3DQEBBQUAA4IBAQDEDbCBvdfs/lEsEJk0xGB6OghO\r\ngUAHplmWJEXoHb0h7p2QAIcs/QKorMDi35bu/J2vbVRIP7i5wBTZLIHBaf03mqAR\r\nZxYFy/ymyROVmmKl1x1/ry1aumomfU86UN8hCkvJc+40V2KC1lCZcjqPWufECjpo\r\n37QXFA+te/rVyVgvoMhIq+zOBZEK0/2mYGCwlpEQo3HElCQIwV9upChOHnQ2jZyF\r\nCmoUyv0JNr8dkh6H7+KTV6FzWaC+b6Liier9bpfEq/zDAp41GR+L/pdbaliSDtRg\r\nfPhAyCZqwsXid6HgIyTBqxUpGDGRiuygqghmqlFfppZuAqz02wrNceDHH7Up\r\n-----END CERTIFICATE-----\r\n',
pinned: true,
ignoreTLS: false
/**
* Global app configurations
*/
exports.config = {
cloudUrl: cloudUrl || 'https://keys.whiteout.io',
privkeyServerUrl: keychainUrl || 'https://keychain.whiteout.io',
adminUrl: 'https://admin-node.whiteout.io',
wmailDomain: 'wmail.io',
serverPrivateKeyId: 'EE342F0DDBB0F3BE',
symKeySize: 256,
symIvSize: 96,
asymKeySize: 2048,
workerPath: 'js',
reconnectInterval: 10000,
wmail: {
imap: {
host: 'imap.wmail.io',
port: 993,
secure: true,
ca: '-----BEGIN CERTIFICATE-----\r\nMIIGiTCCBXGgAwIBAgIDAn03MA0GCSqGSIb3DQEBBQUAMIGMMQswCQYDVQQGEwJJ\r\nTDEWMBQGA1UEChMNU3RhcnRDb20gTHRkLjErMCkGA1UECxMiU2VjdXJlIERpZ2l0\r\nYWwgQ2VydGlmaWNhdGUgU2lnbmluZzE4MDYGA1UEAxMvU3RhcnRDb20gQ2xhc3Mg\r\nMiBQcmltYXJ5IEludGVybWVkaWF0ZSBTZXJ2ZXIgQ0EwHhcNMTQwOTIxMTQ0ODQ0\r\nWhcNMTYwOTIxMTE0MzIxWjCBmTEZMBcGA1UEDRMQQ1NIeEdsdlg4Z3lRQ2c3TzEL\r\nMAkGA1UEBhMCREUxDzANBgNVBAgTBkJheWVybjEPMA0GA1UEBxMGTXVuaWNoMRUw\r\nEwYDVQQKEwxUYW5rcmVkIEhhc2UxEzARBgNVBAMUCioud21haWwuaW8xITAfBgkq\r\nhkiG9w0BCQEWEndlYm1hc3RlckB3bWFpbC5pbzCCASIwDQYJKoZIhvcNAQEBBQAD\r\nggEPADCCAQoCggEBAMkbMzFfZLYqfWG8yw2HvuNiFs5ajBFMrjG3fobePE674mPd\r\niFtXqbl3ydQ+umTtbJ6bztgxB3KgrL3lhp6IkD4VxB8YQJoYGhU6YH7FhP4QMm8l\r\ncnFLUZXEbcpcCg1tjL6+vvoTMUWEbV/zNtF/oiJ4AIOKwf0zUMZkTu1FCNrOrvpj\r\n6SAkOdBGzLTOAP5vxP43PfpZPZ4dLL2Be7ENYKXqPs0jSlUWpdT9l4AZG8rHKa2d\r\nccWvRoAsCfvpzGDoMYEx9+a1F1XjNZfzo7yJQcHdaE3Mj1eNqbjZe0+Vmact1S/3\r\nsubdcDVkFSfUMsAB5rH0D5mEhy7sMpt1WWCvOF8CAwEAAaOCAuMwggLfMAkGA1Ud\r\nEwQCMAAwCwYDVR0PBAQDAgOoMB0GA1UdJQQWMBQGCCsGAQUFBwMCBggrBgEFBQcD\r\nATAdBgNVHQ4EFgQUv+9x4PISm5Roq4pdNoaFJeIZFfUwHwYDVR0jBBgwFoAUEdsj\r\nRf1UzGpxb4SKA9e+9wEvJoYwHwYDVR0RBBgwFoIKKi53bWFpbC5pb4IId21haWwu\r\naW8wggFWBgNVHSAEggFNMIIBSTAIBgZngQwBAgIwggE7BgsrBgEEAYG1NwECAzCC\r\nASowLgYIKwYBBQUHAgEWImh0dHA6Ly93d3cuc3RhcnRzc2wuY29tL3BvbGljeS5w\r\nZGYwgfcGCCsGAQUFBwICMIHqMCcWIFN0YXJ0Q29tIENlcnRpZmljYXRpb24gQXV0\r\naG9yaXR5MAMCAQEagb5UaGlzIGNlcnRpZmljYXRlIHdhcyBpc3N1ZWQgYWNjb3Jk\r\naW5nIHRvIHRoZSBDbGFzcyAyIFZhbGlkYXRpb24gcmVxdWlyZW1lbnRzIG9mIHRo\r\nZSBTdGFydENvbSBDQSBwb2xpY3ksIHJlbGlhbmNlIG9ubHkgZm9yIHRoZSBpbnRl\r\nbmRlZCBwdXJwb3NlIGluIGNvbXBsaWFuY2Ugb2YgdGhlIHJlbHlpbmcgcGFydHkg\r\nb2JsaWdhdGlvbnMuMDUGA1UdHwQuMCwwKqAooCaGJGh0dHA6Ly9jcmwuc3RhcnRz\r\nc2wuY29tL2NydDItY3JsLmNybDCBjgYIKwYBBQUHAQEEgYEwfzA5BggrBgEFBQcw\r\nAYYtaHR0cDovL29jc3Auc3RhcnRzc2wuY29tL3N1Yi9jbGFzczIvc2VydmVyL2Nh\r\nMEIGCCsGAQUFBzAChjZodHRwOi8vYWlhLnN0YXJ0c3NsLmNvbS9jZXJ0cy9zdWIu\r\nY2xhc3MyLnNlcnZlci5jYS5jcnQwIwYDVR0SBBwwGoYYaHR0cDovL3d3dy5zdGFy\r\ndHNzbC5jb20vMA0GCSqGSIb3DQEBBQUAA4IBAQDEDbCBvdfs/lEsEJk0xGB6OghO\r\ngUAHplmWJEXoHb0h7p2QAIcs/QKorMDi35bu/J2vbVRIP7i5wBTZLIHBaf03mqAR\r\nZxYFy/ymyROVmmKl1x1/ry1aumomfU86UN8hCkvJc+40V2KC1lCZcjqPWufECjpo\r\n37QXFA+te/rVyVgvoMhIq+zOBZEK0/2mYGCwlpEQo3HElCQIwV9upChOHnQ2jZyF\r\nCmoUyv0JNr8dkh6H7+KTV6FzWaC+b6Liier9bpfEq/zDAp41GR+L/pdbaliSDtRg\r\nfPhAyCZqwsXid6HgIyTBqxUpGDGRiuygqghmqlFfppZuAqz02wrNceDHH7Up\r\n-----END CERTIFICATE-----\r\n',
pinned: true,
ignoreTLS: false
},
smtp: {
host: 'smtp.wmail.io',
port: 465,
secure: true,
ca: '-----BEGIN CERTIFICATE-----\r\nMIIGiTCCBXGgAwIBAgIDAn03MA0GCSqGSIb3DQEBBQUAMIGMMQswCQYDVQQGEwJJ\r\nTDEWMBQGA1UEChMNU3RhcnRDb20gTHRkLjErMCkGA1UECxMiU2VjdXJlIERpZ2l0\r\nYWwgQ2VydGlmaWNhdGUgU2lnbmluZzE4MDYGA1UEAxMvU3RhcnRDb20gQ2xhc3Mg\r\nMiBQcmltYXJ5IEludGVybWVkaWF0ZSBTZXJ2ZXIgQ0EwHhcNMTQwOTIxMTQ0ODQ0\r\nWhcNMTYwOTIxMTE0MzIxWjCBmTEZMBcGA1UEDRMQQ1NIeEdsdlg4Z3lRQ2c3TzEL\r\nMAkGA1UEBhMCREUxDzANBgNVBAgTBkJheWVybjEPMA0GA1UEBxMGTXVuaWNoMRUw\r\nEwYDVQQKEwxUYW5rcmVkIEhhc2UxEzARBgNVBAMUCioud21haWwuaW8xITAfBgkq\r\nhkiG9w0BCQEWEndlYm1hc3RlckB3bWFpbC5pbzCCASIwDQYJKoZIhvcNAQEBBQAD\r\nggEPADCCAQoCggEBAMkbMzFfZLYqfWG8yw2HvuNiFs5ajBFMrjG3fobePE674mPd\r\niFtXqbl3ydQ+umTtbJ6bztgxB3KgrL3lhp6IkD4VxB8YQJoYGhU6YH7FhP4QMm8l\r\ncnFLUZXEbcpcCg1tjL6+vvoTMUWEbV/zNtF/oiJ4AIOKwf0zUMZkTu1FCNrOrvpj\r\n6SAkOdBGzLTOAP5vxP43PfpZPZ4dLL2Be7ENYKXqPs0jSlUWpdT9l4AZG8rHKa2d\r\nccWvRoAsCfvpzGDoMYEx9+a1F1XjNZfzo7yJQcHdaE3Mj1eNqbjZe0+Vmact1S/3\r\nsubdcDVkFSfUMsAB5rH0D5mEhy7sMpt1WWCvOF8CAwEAAaOCAuMwggLfMAkGA1Ud\r\nEwQCMAAwCwYDVR0PBAQDAgOoMB0GA1UdJQQWMBQGCCsGAQUFBwMCBggrBgEFBQcD\r\nATAdBgNVHQ4EFgQUv+9x4PISm5Roq4pdNoaFJeIZFfUwHwYDVR0jBBgwFoAUEdsj\r\nRf1UzGpxb4SKA9e+9wEvJoYwHwYDVR0RBBgwFoIKKi53bWFpbC5pb4IId21haWwu\r\naW8wggFWBgNVHSAEggFNMIIBSTAIBgZngQwBAgIwggE7BgsrBgEEAYG1NwECAzCC\r\nASowLgYIKwYBBQUHAgEWImh0dHA6Ly93d3cuc3RhcnRzc2wuY29tL3BvbGljeS5w\r\nZGYwgfcGCCsGAQUFBwICMIHqMCcWIFN0YXJ0Q29tIENlcnRpZmljYXRpb24gQXV0\r\naG9yaXR5MAMCAQEagb5UaGlzIGNlcnRpZmljYXRlIHdhcyBpc3N1ZWQgYWNjb3Jk\r\naW5nIHRvIHRoZSBDbGFzcyAyIFZhbGlkYXRpb24gcmVxdWlyZW1lbnRzIG9mIHRo\r\nZSBTdGFydENvbSBDQSBwb2xpY3ksIHJlbGlhbmNlIG9ubHkgZm9yIHRoZSBpbnRl\r\nbmRlZCBwdXJwb3NlIGluIGNvbXBsaWFuY2Ugb2YgdGhlIHJlbHlpbmcgcGFydHkg\r\nb2JsaWdhdGlvbnMuMDUGA1UdHwQuMCwwKqAooCaGJGh0dHA6Ly9jcmwuc3RhcnRz\r\nc2wuY29tL2NydDItY3JsLmNybDCBjgYIKwYBBQUHAQEEgYEwfzA5BggrBgEFBQcw\r\nAYYtaHR0cDovL29jc3Auc3RhcnRzc2wuY29tL3N1Yi9jbGFzczIvc2VydmVyL2Nh\r\nMEIGCCsGAQUFBzAChjZodHRwOi8vYWlhLnN0YXJ0c3NsLmNvbS9jZXJ0cy9zdWIu\r\nY2xhc3MyLnNlcnZlci5jYS5jcnQwIwYDVR0SBBwwGoYYaHR0cDovL3d3dy5zdGFy\r\ndHNzbC5jb20vMA0GCSqGSIb3DQEBBQUAA4IBAQDEDbCBvdfs/lEsEJk0xGB6OghO\r\ngUAHplmWJEXoHb0h7p2QAIcs/QKorMDi35bu/J2vbVRIP7i5wBTZLIHBaf03mqAR\r\nZxYFy/ymyROVmmKl1x1/ry1aumomfU86UN8hCkvJc+40V2KC1lCZcjqPWufECjpo\r\n37QXFA+te/rVyVgvoMhIq+zOBZEK0/2mYGCwlpEQo3HElCQIwV9upChOHnQ2jZyF\r\nCmoUyv0JNr8dkh6H7+KTV6FzWaC+b6Liier9bpfEq/zDAp41GR+L/pdbaliSDtRg\r\nfPhAyCZqwsXid6HgIyTBqxUpGDGRiuygqghmqlFfppZuAqz02wrNceDHH7Up\r\n-----END CERTIFICATE-----\r\n',
pinned: true,
ignoreTLS: false
}
},
gmail: {
clientId: clientId || '440907777130.apps.googleusercontent.com',
imap: {
host: 'imap.gmail.com',
port: 993,
secure: true,
ca: '-----BEGIN CERTIFICATE-----\r\nMIIEBDCCAuygAwIBAgIDAjppMA0GCSqGSIb3DQEBBQUAMEIxCzAJBgNVBAYTAlVT\r\nMRYwFAYDVQQKEw1HZW9UcnVzdCBJbmMuMRswGQYDVQQDExJHZW9UcnVzdCBHbG9i\r\nYWwgQ0EwHhcNMTMwNDA1MTUxNTU1WhcNMTUwNDA0MTUxNTU1WjBJMQswCQYDVQQG\r\nEwJVUzETMBEGA1UEChMKR29vZ2xlIEluYzElMCMGA1UEAxMcR29vZ2xlIEludGVy\r\nbmV0IEF1dGhvcml0eSBHMjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEB\r\nAJwqBHdc2FCROgajguDYUEi8iT/xGXAaiEZ+4I/F8YnOIe5a/mENtzJEiaB0C1NP\r\nVaTOgmKV7utZX8bhBYASxF6UP7xbSDj0U/ck5vuR6RXEz/RTDfRK/J9U3n2+oGtv\r\nh8DQUB8oMANA2ghzUWx//zo8pzcGjr1LEQTrfSTe5vn8MXH7lNVg8y5Kr0LSy+rE\r\nahqyzFPdFUuLH8gZYR/Nnag+YyuENWllhMgZxUYi+FOVvuOAShDGKuy6lyARxzmZ\r\nEASg8GF6lSWMTlJ14rbtCMoU/M4iarNOz0YDl5cDfsCx3nuvRTPPuj5xt970JSXC\r\nDTWJnZ37DhF5iR43xa+OcmkCAwEAAaOB+zCB+DAfBgNVHSMEGDAWgBTAephojYn7\r\nqwVkDBF9qn1luMrMTjAdBgNVHQ4EFgQUSt0GFhu89mi1dvWBtrtiGrpagS8wEgYD\r\nVR0TAQH/BAgwBgEB/wIBADAOBgNVHQ8BAf8EBAMCAQYwOgYDVR0fBDMwMTAvoC2g\r\nK4YpaHR0cDovL2NybC5nZW90cnVzdC5jb20vY3Jscy9ndGdsb2JhbC5jcmwwPQYI\r\nKwYBBQUHAQEEMTAvMC0GCCsGAQUFBzABhiFodHRwOi8vZ3RnbG9iYWwtb2NzcC5n\r\nZW90cnVzdC5jb20wFwYDVR0gBBAwDjAMBgorBgEEAdZ5AgUBMA0GCSqGSIb3DQEB\r\nBQUAA4IBAQA21waAESetKhSbOHezI6B1WLuxfoNCunLaHtiONgaX4PCVOzf9G0JY\r\n/iLIa704XtE7JW4S615ndkZAkNoUyHgN7ZVm2o6Gb4ChulYylYbc3GrKBIxbf/a/\r\nzG+FA1jDaFETzf3I93k9mTXwVqO94FntT0QJo544evZG0R0SnU++0ED8Vf4GXjza\r\nHFa9llF7b1cq26KqltyMdMKVvvBulRP/F/A8rLIQjcxz++iPAsbw+zOzlTvjwsto\r\nWHPbqCRiOwY1nQ2pM714A5AuTHhdUDqB1O6gyHA43LL5Z/qHQF1hwFGPa4NrzQU6\r\nyuGnBXj8ytqU0CwIPX4WecigUCAkVDNx\r\n-----END CERTIFICATE-----',
pinned: true,
ignoreTLS: false
},
smtp: {
host: 'smtp.gmail.com',
port: 465,
secure: true,
ca: '-----BEGIN CERTIFICATE-----\r\nMIIEBDCCAuygAwIBAgIDAjppMA0GCSqGSIb3DQEBBQUAMEIxCzAJBgNVBAYTAlVT\r\nMRYwFAYDVQQKEw1HZW9UcnVzdCBJbmMuMRswGQYDVQQDExJHZW9UcnVzdCBHbG9i\r\nYWwgQ0EwHhcNMTMwNDA1MTUxNTU1WhcNMTUwNDA0MTUxNTU1WjBJMQswCQYDVQQG\r\nEwJVUzETMBEGA1UEChMKR29vZ2xlIEluYzElMCMGA1UEAxMcR29vZ2xlIEludGVy\r\nbmV0IEF1dGhvcml0eSBHMjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEB\r\nAJwqBHdc2FCROgajguDYUEi8iT/xGXAaiEZ+4I/F8YnOIe5a/mENtzJEiaB0C1NP\r\nVaTOgmKV7utZX8bhBYASxF6UP7xbSDj0U/ck5vuR6RXEz/RTDfRK/J9U3n2+oGtv\r\nh8DQUB8oMANA2ghzUWx//zo8pzcGjr1LEQTrfSTe5vn8MXH7lNVg8y5Kr0LSy+rE\r\nahqyzFPdFUuLH8gZYR/Nnag+YyuENWllhMgZxUYi+FOVvuOAShDGKuy6lyARxzmZ\r\nEASg8GF6lSWMTlJ14rbtCMoU/M4iarNOz0YDl5cDfsCx3nuvRTPPuj5xt970JSXC\r\nDTWJnZ37DhF5iR43xa+OcmkCAwEAAaOB+zCB+DAfBgNVHSMEGDAWgBTAephojYn7\r\nqwVkDBF9qn1luMrMTjAdBgNVHQ4EFgQUSt0GFhu89mi1dvWBtrtiGrpagS8wEgYD\r\nVR0TAQH/BAgwBgEB/wIBADAOBgNVHQ8BAf8EBAMCAQYwOgYDVR0fBDMwMTAvoC2g\r\nK4YpaHR0cDovL2NybC5nZW90cnVzdC5jb20vY3Jscy9ndGdsb2JhbC5jcmwwPQYI\r\nKwYBBQUHAQEEMTAvMC0GCCsGAQUFBzABhiFodHRwOi8vZ3RnbG9iYWwtb2NzcC5n\r\nZW90cnVzdC5jb20wFwYDVR0gBBAwDjAMBgorBgEEAdZ5AgUBMA0GCSqGSIb3DQEB\r\nBQUAA4IBAQA21waAESetKhSbOHezI6B1WLuxfoNCunLaHtiONgaX4PCVOzf9G0JY\r\n/iLIa704XtE7JW4S615ndkZAkNoUyHgN7ZVm2o6Gb4ChulYylYbc3GrKBIxbf/a/\r\nzG+FA1jDaFETzf3I93k9mTXwVqO94FntT0QJo544evZG0R0SnU++0ED8Vf4GXjza\r\nHFa9llF7b1cq26KqltyMdMKVvvBulRP/F/A8rLIQjcxz++iPAsbw+zOzlTvjwsto\r\nWHPbqCRiOwY1nQ2pM714A5AuTHhdUDqB1O6gyHA43LL5Z/qHQF1hwFGPa4NrzQU6\r\nyuGnBXj8ytqU0CwIPX4WecigUCAkVDNx\r\n-----END CERTIFICATE-----',
pinned: true,
ignoreTLS: false
},
ignoreUploadOnSent: true
smtp: {
host: 'smtp.wmail.io',
port: 465,
secure: true,
ca: '-----BEGIN CERTIFICATE-----\r\nMIIGiTCCBXGgAwIBAgIDAn03MA0GCSqGSIb3DQEBBQUAMIGMMQswCQYDVQQGEwJJ\r\nTDEWMBQGA1UEChMNU3RhcnRDb20gTHRkLjErMCkGA1UECxMiU2VjdXJlIERpZ2l0\r\nYWwgQ2VydGlmaWNhdGUgU2lnbmluZzE4MDYGA1UEAxMvU3RhcnRDb20gQ2xhc3Mg\r\nMiBQcmltYXJ5IEludGVybWVkaWF0ZSBTZXJ2ZXIgQ0EwHhcNMTQwOTIxMTQ0ODQ0\r\nWhcNMTYwOTIxMTE0MzIxWjCBmTEZMBcGA1UEDRMQQ1NIeEdsdlg4Z3lRQ2c3TzEL\r\nMAkGA1UEBhMCREUxDzANBgNVBAgTBkJheWVybjEPMA0GA1UEBxMGTXVuaWNoMRUw\r\nEwYDVQQKEwxUYW5rcmVkIEhhc2UxEzARBgNVBAMUCioud21haWwuaW8xITAfBgkq\r\nhkiG9w0BCQEWEndlYm1hc3RlckB3bWFpbC5pbzCCASIwDQYJKoZIhvcNAQEBBQAD\r\nggEPADCCAQoCggEBAMkbMzFfZLYqfWG8yw2HvuNiFs5ajBFMrjG3fobePE674mPd\r\niFtXqbl3ydQ+umTtbJ6bztgxB3KgrL3lhp6IkD4VxB8YQJoYGhU6YH7FhP4QMm8l\r\ncnFLUZXEbcpcCg1tjL6+vvoTMUWEbV/zNtF/oiJ4AIOKwf0zUMZkTu1FCNrOrvpj\r\n6SAkOdBGzLTOAP5vxP43PfpZPZ4dLL2Be7ENYKXqPs0jSlUWpdT9l4AZG8rHKa2d\r\nccWvRoAsCfvpzGDoMYEx9+a1F1XjNZfzo7yJQcHdaE3Mj1eNqbjZe0+Vmact1S/3\r\nsubdcDVkFSfUMsAB5rH0D5mEhy7sMpt1WWCvOF8CAwEAAaOCAuMwggLfMAkGA1Ud\r\nEwQCMAAwCwYDVR0PBAQDAgOoMB0GA1UdJQQWMBQGCCsGAQUFBwMCBggrBgEFBQcD\r\nATAdBgNVHQ4EFgQUv+9x4PISm5Roq4pdNoaFJeIZFfUwHwYDVR0jBBgwFoAUEdsj\r\nRf1UzGpxb4SKA9e+9wEvJoYwHwYDVR0RBBgwFoIKKi53bWFpbC5pb4IId21haWwu\r\naW8wggFWBgNVHSAEggFNMIIBSTAIBgZngQwBAgIwggE7BgsrBgEEAYG1NwECAzCC\r\nASowLgYIKwYBBQUHAgEWImh0dHA6Ly93d3cuc3RhcnRzc2wuY29tL3BvbGljeS5w\r\nZGYwgfcGCCsGAQUFBwICMIHqMCcWIFN0YXJ0Q29tIENlcnRpZmljYXRpb24gQXV0\r\naG9yaXR5MAMCAQEagb5UaGlzIGNlcnRpZmljYXRlIHdhcyBpc3N1ZWQgYWNjb3Jk\r\naW5nIHRvIHRoZSBDbGFzcyAyIFZhbGlkYXRpb24gcmVxdWlyZW1lbnRzIG9mIHRo\r\nZSBTdGFydENvbSBDQSBwb2xpY3ksIHJlbGlhbmNlIG9ubHkgZm9yIHRoZSBpbnRl\r\nbmRlZCBwdXJwb3NlIGluIGNvbXBsaWFuY2Ugb2YgdGhlIHJlbHlpbmcgcGFydHkg\r\nb2JsaWdhdGlvbnMuMDUGA1UdHwQuMCwwKqAooCaGJGh0dHA6Ly9jcmwuc3RhcnRz\r\nc2wuY29tL2NydDItY3JsLmNybDCBjgYIKwYBBQUHAQEEgYEwfzA5BggrBgEFBQcw\r\nAYYtaHR0cDovL29jc3Auc3RhcnRzc2wuY29tL3N1Yi9jbGFzczIvc2VydmVyL2Nh\r\nMEIGCCsGAQUFBzAChjZodHRwOi8vYWlhLnN0YXJ0c3NsLmNvbS9jZXJ0cy9zdWIu\r\nY2xhc3MyLnNlcnZlci5jYS5jcnQwIwYDVR0SBBwwGoYYaHR0cDovL3d3dy5zdGFy\r\ndHNzbC5jb20vMA0GCSqGSIb3DQEBBQUAA4IBAQDEDbCBvdfs/lEsEJk0xGB6OghO\r\ngUAHplmWJEXoHb0h7p2QAIcs/QKorMDi35bu/J2vbVRIP7i5wBTZLIHBaf03mqAR\r\nZxYFy/ymyROVmmKl1x1/ry1aumomfU86UN8hCkvJc+40V2KC1lCZcjqPWufECjpo\r\n37QXFA+te/rVyVgvoMhIq+zOBZEK0/2mYGCwlpEQo3HElCQIwV9upChOHnQ2jZyF\r\nCmoUyv0JNr8dkh6H7+KTV6FzWaC+b6Liier9bpfEq/zDAp41GR+L/pdbaliSDtRg\r\nfPhAyCZqwsXid6HgIyTBqxUpGDGRiuygqghmqlFfppZuAqz02wrNceDHH7Up\r\n-----END CERTIFICATE-----\r\n',
pinned: true,
ignoreTLS: false
}
},
gmail: {
clientId: clientId || '440907777130.apps.googleusercontent.com',
imap: {
host: 'imap.gmail.com',
port: 993,
secure: true,
ca: '-----BEGIN CERTIFICATE-----\r\nMIIEBDCCAuygAwIBAgIDAjppMA0GCSqGSIb3DQEBBQUAMEIxCzAJBgNVBAYTAlVT\r\nMRYwFAYDVQQKEw1HZW9UcnVzdCBJbmMuMRswGQYDVQQDExJHZW9UcnVzdCBHbG9i\r\nYWwgQ0EwHhcNMTMwNDA1MTUxNTU1WhcNMTUwNDA0MTUxNTU1WjBJMQswCQYDVQQG\r\nEwJVUzETMBEGA1UEChMKR29vZ2xlIEluYzElMCMGA1UEAxMcR29vZ2xlIEludGVy\r\nbmV0IEF1dGhvcml0eSBHMjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEB\r\nAJwqBHdc2FCROgajguDYUEi8iT/xGXAaiEZ+4I/F8YnOIe5a/mENtzJEiaB0C1NP\r\nVaTOgmKV7utZX8bhBYASxF6UP7xbSDj0U/ck5vuR6RXEz/RTDfRK/J9U3n2+oGtv\r\nh8DQUB8oMANA2ghzUWx//zo8pzcGjr1LEQTrfSTe5vn8MXH7lNVg8y5Kr0LSy+rE\r\nahqyzFPdFUuLH8gZYR/Nnag+YyuENWllhMgZxUYi+FOVvuOAShDGKuy6lyARxzmZ\r\nEASg8GF6lSWMTlJ14rbtCMoU/M4iarNOz0YDl5cDfsCx3nuvRTPPuj5xt970JSXC\r\nDTWJnZ37DhF5iR43xa+OcmkCAwEAAaOB+zCB+DAfBgNVHSMEGDAWgBTAephojYn7\r\nqwVkDBF9qn1luMrMTjAdBgNVHQ4EFgQUSt0GFhu89mi1dvWBtrtiGrpagS8wEgYD\r\nVR0TAQH/BAgwBgEB/wIBADAOBgNVHQ8BAf8EBAMCAQYwOgYDVR0fBDMwMTAvoC2g\r\nK4YpaHR0cDovL2NybC5nZW90cnVzdC5jb20vY3Jscy9ndGdsb2JhbC5jcmwwPQYI\r\nKwYBBQUHAQEEMTAvMC0GCCsGAQUFBzABhiFodHRwOi8vZ3RnbG9iYWwtb2NzcC5n\r\nZW90cnVzdC5jb20wFwYDVR0gBBAwDjAMBgorBgEEAdZ5AgUBMA0GCSqGSIb3DQEB\r\nBQUAA4IBAQA21waAESetKhSbOHezI6B1WLuxfoNCunLaHtiONgaX4PCVOzf9G0JY\r\n/iLIa704XtE7JW4S615ndkZAkNoUyHgN7ZVm2o6Gb4ChulYylYbc3GrKBIxbf/a/\r\nzG+FA1jDaFETzf3I93k9mTXwVqO94FntT0QJo544evZG0R0SnU++0ED8Vf4GXjza\r\nHFa9llF7b1cq26KqltyMdMKVvvBulRP/F/A8rLIQjcxz++iPAsbw+zOzlTvjwsto\r\nWHPbqCRiOwY1nQ2pM714A5AuTHhdUDqB1O6gyHA43LL5Z/qHQF1hwFGPa4NrzQU6\r\nyuGnBXj8ytqU0CwIPX4WecigUCAkVDNx\r\n-----END CERTIFICATE-----',
pinned: true,
ignoreTLS: false
},
yahoo: {
imap: {
host: 'imap.mail.yahoo.com',
port: 993,
secure: true,
ca: '-----BEGIN CERTIFICATE-----\r\nMIIFUjCCBDqgAwIBAgIQdThnkckQvgouzHOsQA7ctTANBgkqhkiG9w0BAQUFADCB\r\ntTELMAkGA1UEBhMCVVMxFzAVBgNVBAoTDlZlcmlTaWduLCBJbmMuMR8wHQYDVQQL\r\nExZWZXJpU2lnbiBUcnVzdCBOZXR3b3JrMTswOQYDVQQLEzJUZXJtcyBvZiB1c2Ug\r\nYXQgaHR0cHM6Ly93d3cudmVyaXNpZ24uY29tL3JwYSAoYykxMDEvMC0GA1UEAxMm\r\nVmVyaVNpZ24gQ2xhc3MgMyBTZWN1cmUgU2VydmVyIENBIC0gRzMwHhcNMTQwNDIy\r\nMDAwMDAwWhcNMTUwNDIzMjM1OTU5WjCBjDELMAkGA1UEBhMCVVMxEzARBgNVBAgT\r\nCkNhbGlmb3JuaWExEjAQBgNVBAcUCVN1bm55dmFsZTETMBEGA1UEChQKWWFob28g\r\nSW5jLjEfMB0GA1UECxQWSW5mb3JtYXRpb24gVGVjaG5vbG9neTEeMBwGA1UEAxQV\r\nKi5pbWFwLm1haWwueWFob28uY29tMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIB\r\nCgKCAQEAw36HN2sgtMNQ0TZlqGgfqInK/UT6y3ZgqDdmFRU9D5D5675hfxcwJoS+\r\nb0hIn/UixbZDpSLhNkjAkOTAbFEFWar7628D2dU5WtCUlFMiwg2TA0Un8B9EbUi5\r\nwDrqzXDyABVnBVR5I2eKwr5cuB9ldjxAabcCyqQhVKdH0+IskRpUrvxAb84uQtJg\r\nJyNieOZAWdxg9fkubk1YKw/MgJHnaY8P4lUlYY8fIY39d6gW6My8oT0IersrH1X1\r\n/oCmqUIGM1PawXBvvpPKYdI4fCH75/UaEQ41BFSUn1NsinFYZUPlVcBCOvLFEOQi\r\nuU+4Tjybq3x7NNhd3uBxfm4jo4h5zQIDAQABo4IBgzCCAX8wNQYDVR0RBC4wLIIV\r\nKi5pbWFwLm1haWwueWFob28uY29tghNpbWFwLm1haWwueWFob28uY29tMAkGA1Ud\r\nEwQCMAAwDgYDVR0PAQH/BAQDAgWgMB0GA1UdJQQWMBQGCCsGAQUFBwMBBggrBgEF\r\nBQcDAjBlBgNVHSAEXjBcMFoGCmCGSAGG+EUBBzYwTDAjBggrBgEFBQcCARYXaHR0\r\ncHM6Ly9kLnN5bWNiLmNvbS9jcHMwJQYIKwYBBQUHAgIwGRoXaHR0cHM6Ly9kLnN5\r\nbWNiLmNvbS9ycGEwHwYDVR0jBBgwFoAUDURcFlNEwYJ+HSCrJfQBY9i+eaUwKwYD\r\nVR0fBCQwIjAgoB6gHIYaaHR0cDovL3NkLnN5bWNiLmNvbS9zZC5jcmwwVwYIKwYB\r\nBQUHAQEESzBJMB8GCCsGAQUFBzABhhNodHRwOi8vc2Quc3ltY2QuY29tMCYGCCsG\r\nAQUFBzAChhpodHRwOi8vc2Quc3ltY2IuY29tL3NkLmNydDANBgkqhkiG9w0BAQUF\r\nAAOCAQEAVxsglXJTtBTCoTwOd6j0iJQ+P9cxFVqHcmbshEfEQBlPwr4Sp9tLJ4kj\r\nfVi0XorWU6e6e57dtYtxpcPz+6WNSNKT0B0IBOTUTIBwSLJMHxEZI6gSS/fo1agt\r\n81B06rB8Rhn4yHwyDO/9uRvXbNYiEgpa5e6gIpXY6h6p1HscQMcuROaUA9ETvGd8\r\nDKG4XSZE7QAF9iB9WSLa/IQUD4sGMDaMp2q4XkoWZTnyL1bEDKwUvw9Z17PxVmrF\r\n8c7S5HTNU+1kyZw2LJRu3SgtsYXSWA88WFiKUPuqU+EBXmbrwLAwLAJ6mVc2bGFC\r\ng5fLGbtTscaARBlb1u3Iee2Fd419jg==\r\n-----END CERTIFICATE-----',
pinned: true,
ignoreTLS: false
},
smtp: {
host: 'smtp.mail.yahoo.com',
port: 465,
secure: true,
ca: '-----BEGIN CERTIFICATE-----\r\nMIIHITCCBgmgAwIBAgIQBlVSvxlwsqw8Kc8eVV5EKTANBgkqhkiG9w0BAQUFADBm\r\nMQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3\r\nd3cuZGlnaWNlcnQuY29tMSUwIwYDVQQDExxEaWdpQ2VydCBIaWdoIEFzc3VyYW5j\r\nZSBDQS0zMB4XDTE0MDYwNTAwMDAwMFoXDTE1MDYyMzEyMDAwMFowYjELMAkGA1UE\r\nBhMCVVMxCzAJBgNVBAgTAkNBMRIwEAYDVQQHEwlTdW5ueXZhbGUxFDASBgNVBAoM\r\nC1lhaG9vISBJbmMuMRwwGgYDVQQDExNzbXRwLm1haWwueWFob28uY29tMIIBIjAN\r\nBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA339OSxTIuW6NVqKwYHFBbg4/tmEx\r\nMYpzohtObPaWgvLTsYdce+WlQFBU5rxaCMUtVsVAGJWs5uQRDVHXRLPnOWe+foqZ\r\n5ItgjYIQc1XeMv6BZEeaF3Bum5ehUyLB3y48cjbWxma1QlkZ8XAK0f9AS7ySWAen\r\no5OXJlMFAqXGtKMAhY46dzSY0wjgdrvgiomFRy0iQKV1DxGsXoXSMEszlUTQoNQ3\r\nWTsiA3O//cdWE06wWeA3/90cb7QkU3KflSoyfi878BQGpPR1L+rLNsqnc8QuucbD\r\nz4Q++rxGqgg4QYrOtmZfAn96TXhPWCwKld6FN+f3uV5ITOBFx44M5v1ILQIDAQAB\r\no4IDzTCCA8kwHwYDVR0jBBgwFoAUUOpzidsp+xCPnuUBINTeeZlIg/cwHQYDVR0O\r\nBBYEFEhOpSFQjO/QrSNfVwyInZUhyzhsMIICJAYDVR0RBIICGzCCAheCE3NtdHAu\r\nbWFpbC55YWhvby5jb22CFnNtdHAubWFpbC55YWhvby5jb20uYXKCFnNtdHAubWFp\r\nbC55YWhvby5jb20uYXWCFnNtdHAubWFpbC55YWhvby5jb20uYnKCFnNtdHAubWFp\r\nbC55YWhvby5jb20uY26CFnNtdHAubWFpbC55YWhvby5jb20uaGuCFnNtdHAubWFp\r\nbC55YWhvby5jb20ubXmCFnNtdHAubWFpbC55YWhvby5jb20ucGiCFnNtdHAubWFp\r\nbC55YWhvby5jb20uc2eCFnNtdHAubWFpbC55YWhvby5jb20udHeCFnNtdHAubWFp\r\nbC55YWhvby5jb20udm6CFXNtdHAubWFpbC55YWhvby5jby5pZIIVc210cC5tYWls\r\nLnlhaG9vLmNvLmlughVzbXRwLm1haWwueWFob28uY28ua3KCFXNtdHAubWFpbC55\r\nYWhvby5jby50aIIVc210cC5tYWlsLnlhaG9vLmNvLnVrghJzbXRwLm1haWwueWFo\r\nb28uY2GCEnNtdHAubWFpbC55YWhvby5jboISc210cC5tYWlsLnlhaG9vLmRlghJz\r\nbXRwLm1haWwueWFob28uZXOCEnNtdHAubWFpbC55YWhvby5mcoISc210cC5tYWls\r\nLnlhaG9vLml0gg9zbXRwLnk3bWFpbC5jb22CFHNtdHAuY29ycmVvLnlhaG9vLmVz\r\nMA4GA1UdDwEB/wQEAwIFoDAdBgNVHSUEFjAUBggrBgEFBQcDAQYIKwYBBQUHAwIw\r\nYQYDVR0fBFowWDAqoCigJoYkaHR0cDovL2NybDMuZGlnaWNlcnQuY29tL2NhMy1n\r\nMjguY3JsMCqgKKAmhiRodHRwOi8vY3JsNC5kaWdpY2VydC5jb20vY2EzLWcyOC5j\r\ncmwwQgYDVR0gBDswOTA3BglghkgBhv1sAQEwKjAoBggrBgEFBQcCARYcaHR0cHM6\r\nLy93d3cuZGlnaWNlcnQuY29tL0NQUzB7BggrBgEFBQcBAQRvMG0wJAYIKwYBBQUH\r\nMAGGGGh0dHA6Ly9vY3NwLmRpZ2ljZXJ0LmNvbTBFBggrBgEFBQcwAoY5aHR0cDov\r\nL2NhY2VydHMuZGlnaWNlcnQuY29tL0RpZ2lDZXJ0SGlnaEFzc3VyYW5jZUNBLTMu\r\nY3J0MAwGA1UdEwEB/wQCMAAwDQYJKoZIhvcNAQEFBQADggEBABshjUND4hADHGfe\r\ncV9XGXcr9h3u7mT5kgdcgGFhcrFAlwkzt0NUCbuN0X8DWHE72Tpb3zRE25CmUUJe\r\nktBbq9PQb4b5/Wt1htAEw0qUs3BsUbqejK9OHJ/11Jn2ek4+SLJuYlijzc7KM3F/\r\nyz7ZTZtKR0PglkXfqbpvWYGabYpfL2FRLoJ7alTLsMJcFxbSLYcAIMxufj7RyTBJ\r\nbKgRJl4wmP4+Zc2Q1p59mENY0u5HqVAAOmWc0jNb0/31+tRr5f6EgXxK++7TQOpF\r\n0TOaFsXlzRlpKfmIbzVr2nfwghV5/bRZj96TK3g1OoOz4C8ksK4INHnUdTAqZ18M\r\nvHpnJw4=\r\n-----END CERTIFICATE-----',
pinned: true,
ignoreTLS: false
}
smtp: {
host: 'smtp.gmail.com',
port: 465,
secure: true,
ca: '-----BEGIN CERTIFICATE-----\r\nMIIEBDCCAuygAwIBAgIDAjppMA0GCSqGSIb3DQEBBQUAMEIxCzAJBgNVBAYTAlVT\r\nMRYwFAYDVQQKEw1HZW9UcnVzdCBJbmMuMRswGQYDVQQDExJHZW9UcnVzdCBHbG9i\r\nYWwgQ0EwHhcNMTMwNDA1MTUxNTU1WhcNMTUwNDA0MTUxNTU1WjBJMQswCQYDVQQG\r\nEwJVUzETMBEGA1UEChMKR29vZ2xlIEluYzElMCMGA1UEAxMcR29vZ2xlIEludGVy\r\nbmV0IEF1dGhvcml0eSBHMjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEB\r\nAJwqBHdc2FCROgajguDYUEi8iT/xGXAaiEZ+4I/F8YnOIe5a/mENtzJEiaB0C1NP\r\nVaTOgmKV7utZX8bhBYASxF6UP7xbSDj0U/ck5vuR6RXEz/RTDfRK/J9U3n2+oGtv\r\nh8DQUB8oMANA2ghzUWx//zo8pzcGjr1LEQTrfSTe5vn8MXH7lNVg8y5Kr0LSy+rE\r\nahqyzFPdFUuLH8gZYR/Nnag+YyuENWllhMgZxUYi+FOVvuOAShDGKuy6lyARxzmZ\r\nEASg8GF6lSWMTlJ14rbtCMoU/M4iarNOz0YDl5cDfsCx3nuvRTPPuj5xt970JSXC\r\nDTWJnZ37DhF5iR43xa+OcmkCAwEAAaOB+zCB+DAfBgNVHSMEGDAWgBTAephojYn7\r\nqwVkDBF9qn1luMrMTjAdBgNVHQ4EFgQUSt0GFhu89mi1dvWBtrtiGrpagS8wEgYD\r\nVR0TAQH/BAgwBgEB/wIBADAOBgNVHQ8BAf8EBAMCAQYwOgYDVR0fBDMwMTAvoC2g\r\nK4YpaHR0cDovL2NybC5nZW90cnVzdC5jb20vY3Jscy9ndGdsb2JhbC5jcmwwPQYI\r\nKwYBBQUHAQEEMTAvMC0GCCsGAQUFBzABhiFodHRwOi8vZ3RnbG9iYWwtb2NzcC5n\r\nZW90cnVzdC5jb20wFwYDVR0gBBAwDjAMBgorBgEEAdZ5AgUBMA0GCSqGSIb3DQEB\r\nBQUAA4IBAQA21waAESetKhSbOHezI6B1WLuxfoNCunLaHtiONgaX4PCVOzf9G0JY\r\n/iLIa704XtE7JW4S615ndkZAkNoUyHgN7ZVm2o6Gb4ChulYylYbc3GrKBIxbf/a/\r\nzG+FA1jDaFETzf3I93k9mTXwVqO94FntT0QJo544evZG0R0SnU++0ED8Vf4GXjza\r\nHFa9llF7b1cq26KqltyMdMKVvvBulRP/F/A8rLIQjcxz++iPAsbw+zOzlTvjwsto\r\nWHPbqCRiOwY1nQ2pM714A5AuTHhdUDqB1O6gyHA43LL5Z/qHQF1hwFGPa4NrzQU6\r\nyuGnBXj8ytqU0CwIPX4WecigUCAkVDNx\r\n-----END CERTIFICATE-----',
pinned: true,
ignoreTLS: false
},
tonline: {
imap: {
host: 'secureimap.t-online.de',
port: 993,
secure: true,
ca: '-----BEGIN CERTIFICATE-----\r\nMIIGmzCCBYOgAwIBAgIIIbZ3foy9DqgwDQYJKoZIhvcNAQEFBQAwcTELMAkGA1UE\r\nBhMCREUxHDAaBgNVBAoTE0RldXRzY2hlIFRlbGVrb20gQUcxHzAdBgNVBAsTFlQt\r\nVGVsZVNlYyBUcnVzdCBDZW50ZXIxIzAhBgNVBAMTGkRldXRzY2hlIFRlbGVrb20g\r\nUm9vdCBDQSAyMB4XDTEzMDMwMTEzNTgyOVoXDTE5MDcwOTIzNTkwMFowgckxCzAJ\r\nBgNVBAYTAkRFMSUwIwYDVQQKExxULVN5c3RlbXMgSW50ZXJuYXRpb25hbCBHbWJI\r\nMR8wHQYDVQQLExZULVN5c3RlbXMgVHJ1c3QgQ2VudGVyMQwwCgYDVQQIEwNOUlcx\r\nDjAMBgNVBBETBTU3MjUwMRAwDgYDVQQHEwdOZXRwaGVuMSAwHgYDVQQJExdVbnRl\r\ncmUgSW5kdXN0cmllc3RyLiAyMDEgMB4GA1UEAxMXVGVsZVNlYyBTZXJ2ZXJQYXNz\r\nIERFLTEwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCwg+9QiuYAxX9/\r\n4F9XRZrS1o0q+aa9L/5/K3vc+RqPpliiZ24vTkJc1JDpXrbWXS25uT3yzHukQrhI\r\nq0AbcRqNEAeFi5EhUiEM/vtb4BYGHdqfXQ3etgYYcCtJ43NAHaSgsyQ9kyGV2lmM\r\nwkeAX3qZ2CGE9/cR6w+bOogHArBdk2JaHG09myNZDytr6oUbWLjLd/qhC9YzyZSX\r\nbZgE/kh5L5Y6P9paw2pDdn7+Ni4pXzlmoj1k43uiz+h2ibe3DO9dKMZAaEKeyG1O\r\ng0f0r53M8O+8Bm2sXtWelrAgrfFlISgWzO1hkNs12rWpr4c5Ygde/behx9OQmPwp\r\nmS+e3WvTAgMBAAGjggLcMIIC2DAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYEFGJP\r\nE842Z4TNGfygTxmL7xVUATIcMB8GA1UdIwQYMBaAFDHDeRu69VPXF+CJei0XbAqz\r\nK50zMBIGA1UdEwEB/wQIMAYBAf8CAQAwWQYDVR0gBFIwUDBEBgkrBgEEAb1HDQIw\r\nNzA1BggrBgEFBQcCARYpaHR0cDovL3d3dy50ZWxlc2VjLmRlL3NlcnZlcnBhc3Mv\r\nY3BzLmh0bWwwCAYGZ4EMAQICMIHvBgNVHR8EgecwgeQwOqA4oDaGNGh0dHA6Ly9j\r\ncmwuc2VydmVycGFzcy50ZWxlc2VjLmRlL3JsL0RUX1JPT1RfQ0FfMi5jcmwwgaWg\r\ngaKggZ+GgZxsZGFwOi8vbGRhcC5zZXJ2ZXJwYXNzLnRlbGVzZWMuZGUvQ049RGV1\r\ndHNjaGUlMjBUZWxla29tJTIwUm9vdCUyMENBJTIwMixPVT1ULVRlbGVTZWMlMjBU\r\ncnVzdCUyMENlbnRlcixPPURldXRzY2hlJTIwVGVsZWtvbSUyMEFHLEM9REU/QXV0\r\naG9yaXR5UmV2b2NhdGlvbkxpc3QwggEjBggrBgEFBQcBAQSCARUwggERMCoGCCsG\r\nAQUFBzABhh5odHRwOi8vb2NzcDAyLnRlbGVzZWMuZGUvb2NzcHIwQQYIKwYBBQUH\r\nMAKGNWh0dHA6Ly9jcmwuc2VydmVycGFzcy50ZWxlc2VjLmRlL2NydC9EVF9ST09U\r\nX0NBXzIuY2VyMIGfBggrBgEFBQcwAoaBkmxkYXA6Ly9sZGFwLnNlcnZlcnBhc3Mu\r\ndGVsZXNlYy5kZS9DTj1EZXV0c2NoZSUyMFRlbGVrb20lMjBSb290JTIwQ0ElMjAy\r\nLE9VPVQtVGVsZVNlYyUyMFRydXN0JTIwQ2VudGVyLE89RGV1dHNjaGUlMjBUZWxl\r\na29tJTIwQUcsQz1ERT9jQUNlcnRpZmljYXRlMA0GCSqGSIb3DQEBBQUAA4IBAQBO\r\nE04qoEkEc9ad+WwSurVYfcDdjGvpqrtbI89woXDsWLQTMhA7D7jVuls90SJns0vc\r\nK9qoYkEGt0/ZlawLe2lyNWtueHfUf+dgleUunwHYLxuj3jQ2ERzQLVLrswjecRpX\r\nvGAGej89WpGQ9PMq27WGNC5WCmzVC9rk5naFgacsbwKwyjU0LoBArtAQnAAlpHDw\r\nPenv1Pe7MhUkCK0LqdTvkI/AHFzPYg/l5E3j8lQQ8hiKx8U6wf9xVKECLA2RlRqY\r\nUX2rpjQNxnvEq/mEQv3x3mLOEFJ3TAKI+soDgOOi0OG8+ywhm6S+7Z9lTlJ+BcD6\r\noy1MNKd4CQbltHLMTFUH\r\n-----END CERTIFICATE-----',
pinned: true,
ignoreTLS: false
},
smtp: {
host: 'securesmtp.t-online.de',
port: 465,
secure: true,
ca: '-----BEGIN CERTIFICATE-----\r\nMIIGmzCCBYOgAwIBAgIIIbZ3foy9DqgwDQYJKoZIhvcNAQEFBQAwcTELMAkGA1UE\r\nBhMCREUxHDAaBgNVBAoTE0RldXRzY2hlIFRlbGVrb20gQUcxHzAdBgNVBAsTFlQt\r\nVGVsZVNlYyBUcnVzdCBDZW50ZXIxIzAhBgNVBAMTGkRldXRzY2hlIFRlbGVrb20g\r\nUm9vdCBDQSAyMB4XDTEzMDMwMTEzNTgyOVoXDTE5MDcwOTIzNTkwMFowgckxCzAJ\r\nBgNVBAYTAkRFMSUwIwYDVQQKExxULVN5c3RlbXMgSW50ZXJuYXRpb25hbCBHbWJI\r\nMR8wHQYDVQQLExZULVN5c3RlbXMgVHJ1c3QgQ2VudGVyMQwwCgYDVQQIEwNOUlcx\r\nDjAMBgNVBBETBTU3MjUwMRAwDgYDVQQHEwdOZXRwaGVuMSAwHgYDVQQJExdVbnRl\r\ncmUgSW5kdXN0cmllc3RyLiAyMDEgMB4GA1UEAxMXVGVsZVNlYyBTZXJ2ZXJQYXNz\r\nIERFLTEwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCwg+9QiuYAxX9/\r\n4F9XRZrS1o0q+aa9L/5/K3vc+RqPpliiZ24vTkJc1JDpXrbWXS25uT3yzHukQrhI\r\nq0AbcRqNEAeFi5EhUiEM/vtb4BYGHdqfXQ3etgYYcCtJ43NAHaSgsyQ9kyGV2lmM\r\nwkeAX3qZ2CGE9/cR6w+bOogHArBdk2JaHG09myNZDytr6oUbWLjLd/qhC9YzyZSX\r\nbZgE/kh5L5Y6P9paw2pDdn7+Ni4pXzlmoj1k43uiz+h2ibe3DO9dKMZAaEKeyG1O\r\ng0f0r53M8O+8Bm2sXtWelrAgrfFlISgWzO1hkNs12rWpr4c5Ygde/behx9OQmPwp\r\nmS+e3WvTAgMBAAGjggLcMIIC2DAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYEFGJP\r\nE842Z4TNGfygTxmL7xVUATIcMB8GA1UdIwQYMBaAFDHDeRu69VPXF+CJei0XbAqz\r\nK50zMBIGA1UdEwEB/wQIMAYBAf8CAQAwWQYDVR0gBFIwUDBEBgkrBgEEAb1HDQIw\r\nNzA1BggrBgEFBQcCARYpaHR0cDovL3d3dy50ZWxlc2VjLmRlL3NlcnZlcnBhc3Mv\r\nY3BzLmh0bWwwCAYGZ4EMAQICMIHvBgNVHR8EgecwgeQwOqA4oDaGNGh0dHA6Ly9j\r\ncmwuc2VydmVycGFzcy50ZWxlc2VjLmRlL3JsL0RUX1JPT1RfQ0FfMi5jcmwwgaWg\r\ngaKggZ+GgZxsZGFwOi8vbGRhcC5zZXJ2ZXJwYXNzLnRlbGVzZWMuZGUvQ049RGV1\r\ndHNjaGUlMjBUZWxla29tJTIwUm9vdCUyMENBJTIwMixPVT1ULVRlbGVTZWMlMjBU\r\ncnVzdCUyMENlbnRlcixPPURldXRzY2hlJTIwVGVsZWtvbSUyMEFHLEM9REU/QXV0\r\naG9yaXR5UmV2b2NhdGlvbkxpc3QwggEjBggrBgEFBQcBAQSCARUwggERMCoGCCsG\r\nAQUFBzABhh5odHRwOi8vb2NzcDAyLnRlbGVzZWMuZGUvb2NzcHIwQQYIKwYBBQUH\r\nMAKGNWh0dHA6Ly9jcmwuc2VydmVycGFzcy50ZWxlc2VjLmRlL2NydC9EVF9ST09U\r\nX0NBXzIuY2VyMIGfBggrBgEFBQcwAoaBkmxkYXA6Ly9sZGFwLnNlcnZlcnBhc3Mu\r\ndGVsZXNlYy5kZS9DTj1EZXV0c2NoZSUyMFRlbGVrb20lMjBSb290JTIwQ0ElMjAy\r\nLE9VPVQtVGVsZVNlYyUyMFRydXN0JTIwQ2VudGVyLE89RGV1dHNjaGUlMjBUZWxl\r\na29tJTIwQUcsQz1ERT9jQUNlcnRpZmljYXRlMA0GCSqGSIb3DQEBBQUAA4IBAQBO\r\nE04qoEkEc9ad+WwSurVYfcDdjGvpqrtbI89woXDsWLQTMhA7D7jVuls90SJns0vc\r\nK9qoYkEGt0/ZlawLe2lyNWtueHfUf+dgleUunwHYLxuj3jQ2ERzQLVLrswjecRpX\r\nvGAGej89WpGQ9PMq27WGNC5WCmzVC9rk5naFgacsbwKwyjU0LoBArtAQnAAlpHDw\r\nPenv1Pe7MhUkCK0LqdTvkI/AHFzPYg/l5E3j8lQQ8hiKx8U6wf9xVKECLA2RlRqY\r\nUX2rpjQNxnvEq/mEQv3x3mLOEFJ3TAKI+soDgOOi0OG8+ywhm6S+7Z9lTlJ+BcD6\r\noy1MNKd4CQbltHLMTFUH\r\n-----END CERTIFICATE-----',
pinned: true,
ignoreTLS: false
}
ignoreUploadOnSent: true
},
yahoo: {
imap: {
host: 'imap.mail.yahoo.com',
port: 993,
secure: true,
ca: '-----BEGIN CERTIFICATE-----\r\nMIIFUjCCBDqgAwIBAgIQdThnkckQvgouzHOsQA7ctTANBgkqhkiG9w0BAQUFADCB\r\ntTELMAkGA1UEBhMCVVMxFzAVBgNVBAoTDlZlcmlTaWduLCBJbmMuMR8wHQYDVQQL\r\nExZWZXJpU2lnbiBUcnVzdCBOZXR3b3JrMTswOQYDVQQLEzJUZXJtcyBvZiB1c2Ug\r\nYXQgaHR0cHM6Ly93d3cudmVyaXNpZ24uY29tL3JwYSAoYykxMDEvMC0GA1UEAxMm\r\nVmVyaVNpZ24gQ2xhc3MgMyBTZWN1cmUgU2VydmVyIENBIC0gRzMwHhcNMTQwNDIy\r\nMDAwMDAwWhcNMTUwNDIzMjM1OTU5WjCBjDELMAkGA1UEBhMCVVMxEzARBgNVBAgT\r\nCkNhbGlmb3JuaWExEjAQBgNVBAcUCVN1bm55dmFsZTETMBEGA1UEChQKWWFob28g\r\nSW5jLjEfMB0GA1UECxQWSW5mb3JtYXRpb24gVGVjaG5vbG9neTEeMBwGA1UEAxQV\r\nKi5pbWFwLm1haWwueWFob28uY29tMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIB\r\nCgKCAQEAw36HN2sgtMNQ0TZlqGgfqInK/UT6y3ZgqDdmFRU9D5D5675hfxcwJoS+\r\nb0hIn/UixbZDpSLhNkjAkOTAbFEFWar7628D2dU5WtCUlFMiwg2TA0Un8B9EbUi5\r\nwDrqzXDyABVnBVR5I2eKwr5cuB9ldjxAabcCyqQhVKdH0+IskRpUrvxAb84uQtJg\r\nJyNieOZAWdxg9fkubk1YKw/MgJHnaY8P4lUlYY8fIY39d6gW6My8oT0IersrH1X1\r\n/oCmqUIGM1PawXBvvpPKYdI4fCH75/UaEQ41BFSUn1NsinFYZUPlVcBCOvLFEOQi\r\nuU+4Tjybq3x7NNhd3uBxfm4jo4h5zQIDAQABo4IBgzCCAX8wNQYDVR0RBC4wLIIV\r\nKi5pbWFwLm1haWwueWFob28uY29tghNpbWFwLm1haWwueWFob28uY29tMAkGA1Ud\r\nEwQCMAAwDgYDVR0PAQH/BAQDAgWgMB0GA1UdJQQWMBQGCCsGAQUFBwMBBggrBgEF\r\nBQcDAjBlBgNVHSAEXjBcMFoGCmCGSAGG+EUBBzYwTDAjBggrBgEFBQcCARYXaHR0\r\ncHM6Ly9kLnN5bWNiLmNvbS9jcHMwJQYIKwYBBQUHAgIwGRoXaHR0cHM6Ly9kLnN5\r\nbWNiLmNvbS9ycGEwHwYDVR0jBBgwFoAUDURcFlNEwYJ+HSCrJfQBY9i+eaUwKwYD\r\nVR0fBCQwIjAgoB6gHIYaaHR0cDovL3NkLnN5bWNiLmNvbS9zZC5jcmwwVwYIKwYB\r\nBQUHAQEESzBJMB8GCCsGAQUFBzABhhNodHRwOi8vc2Quc3ltY2QuY29tMCYGCCsG\r\nAQUFBzAChhpodHRwOi8vc2Quc3ltY2IuY29tL3NkLmNydDANBgkqhkiG9w0BAQUF\r\nAAOCAQEAVxsglXJTtBTCoTwOd6j0iJQ+P9cxFVqHcmbshEfEQBlPwr4Sp9tLJ4kj\r\nfVi0XorWU6e6e57dtYtxpcPz+6WNSNKT0B0IBOTUTIBwSLJMHxEZI6gSS/fo1agt\r\n81B06rB8Rhn4yHwyDO/9uRvXbNYiEgpa5e6gIpXY6h6p1HscQMcuROaUA9ETvGd8\r\nDKG4XSZE7QAF9iB9WSLa/IQUD4sGMDaMp2q4XkoWZTnyL1bEDKwUvw9Z17PxVmrF\r\n8c7S5HTNU+1kyZw2LJRu3SgtsYXSWA88WFiKUPuqU+EBXmbrwLAwLAJ6mVc2bGFC\r\ng5fLGbtTscaARBlb1u3Iee2Fd419jg==\r\n-----END CERTIFICATE-----',
pinned: true,
ignoreTLS: false
},
outlook: {
imap: {
host: 'imap-mail.outlook.com',
port: 993,
secure: true,
ca: '-----BEGIN CERTIFICATE-----\r\nMIIFQjCCBCqgAwIBAgISESHl0vjrML7zKmGlv42YL75vMA0GCSqGSIb3DQEBBQUA\r\nMF0xCzAJBgNVBAYTAkJFMRkwFwYDVQQKExBHbG9iYWxTaWduIG52LXNhMTMwMQYD\r\nVQQDEypHbG9iYWxTaWduIE9yZ2FuaXphdGlvbiBWYWxpZGF0aW9uIENBIC0gRzIw\r\nHhcNMTMwNDI0MjAzNTA5WhcNMTYwNDI0MjAzNTA5WjBsMQswCQYDVQQGEwJVUzET\r\nMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMV\r\nTWljcm9zb2Z0IENvcnBvcmF0aW9uMRYwFAYDVQQDDA0qLmhvdG1haWwuY29tMIIB\r\nIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAumSiBWrzHZf6WFP5a/j4+K7D\r\n1izLoYKj5Omll0pdxKvKcBRDf+iaIkCbSOPNpx2uWGZdwNwkabYCQavaBf2ebwmS\r\nS8i1CJpHflO+k0qYd5WUi7sSsZ3+6RaCMdLoDIPGyYMQuy7TFtVO7LSt5+qscyyi\r\nET8c3lE2aj/XW13UZvRrV65ZJvMjUtwaDnIcAxGeasYoebLsKdqHQ2uTr4PmNwCc\r\nviGVFSOzkGAoC0PfyqKB2xUWy3Kc5zRI2xvUW8Jb2b/9Ze3g55pIUzKsjpglkQTm\r\nedVPSYYPGNz6Kl/ZshBXdBAk398q1JkSmUaTMa2hJgBbcC+73ax40AJDGJlz+QID\r\nAQABo4IB6zCCAecwDgYDVR0PAQH/BAQDAgWgMEkGA1UdIARCMEAwPgYGZ4EMAQIC\r\nMDQwMgYIKwYBBQUHAgEWJmh0dHBzOi8vd3d3Lmdsb2JhbHNpZ24uY29tL3JlcG9z\r\naXRvcnkvMEAGA1UdEQQ5MDeCDSouaG90bWFpbC5jb22CCioubGl2ZS5jb22CDSou\r\nb3V0bG9vay5jb22CC2hvdG1haWwuY29tMAkGA1UdEwQCMAAwHQYDVR0lBBYwFAYI\r\nKwYBBQUHAwEGCCsGAQUFBwMCMEUGA1UdHwQ+MDwwOqA4oDaGNGh0dHA6Ly9jcmwu\r\nZ2xvYmFsc2lnbi5jb20vZ3MvZ3Nvcmdhbml6YXRpb252YWxnMi5jcmwwgZYGCCsG\r\nAQUFBwEBBIGJMIGGMEcGCCsGAQUFBzAChjtodHRwOi8vc2VjdXJlLmdsb2JhbHNp\r\nZ24uY29tL2NhY2VydC9nc29yZ2FuaXphdGlvbnZhbGcyLmNydDA7BggrBgEFBQcw\r\nAYYvaHR0cDovL29jc3AyLmdsb2JhbHNpZ24uY29tL2dzb3JnYW5pemF0aW9udmFs\r\nZzIwHQYDVR0OBBYEFHbgHqTLsXDt7uMRyE62rnDEfLn9MB8GA1UdIwQYMBaAFF1G\r\nso3ES3Qcu+31c7Y6tziPdZ5+MA0GCSqGSIb3DQEBBQUAA4IBAQByy1+3N6ZRVooI\r\nxqw8Ng+UFz0g7UHkbPEnvTu1uxJ2AojFuP/P1PAk+/6uMRvpPlWg/5uqmOIWxKxJ\r\nLo6xSbkDf4LN+KYwes3XSuPyziZ4QbPnehHhZ0377iiA8fpRJADg9NWKCRHh5aAd\r\ne9QvJUW/GgYkBN+F4yYc2jIjR3Rehv4JYOKS3iXO9OoHsDS2CcCFaS2imgQVfYLg\r\nslBwT/A08PCOhW5huiluSmih7x5Qf7sFDv8jineu6ehKzi8pKnOq4k8G4QiWn38Y\r\nCeiBkkwFOwj7T3M/ITiiSS9DHDGeokj16eBi83Zx3YYiJ9YZvnQ+4GvqJ5eJJ6pR\r\nKKvemr+m\r\n-----END CERTIFICATE-----',
pinned: true,
ignoreTLS: false
},
smtp: {
host: 'smtp-mail.outlook.com',
port: 587,
secure: false,
ca: '-----BEGIN CERTIFICATE-----\r\nMIIFQjCCBCqgAwIBAgISESHl0vjrML7zKmGlv42YL75vMA0GCSqGSIb3DQEBBQUA\r\nMF0xCzAJBgNVBAYTAkJFMRkwFwYDVQQKExBHbG9iYWxTaWduIG52LXNhMTMwMQYD\r\nVQQDEypHbG9iYWxTaWduIE9yZ2FuaXphdGlvbiBWYWxpZGF0aW9uIENBIC0gRzIw\r\nHhcNMTMwNDI0MjAzNTA5WhcNMTYwNDI0MjAzNTA5WjBsMQswCQYDVQQGEwJVUzET\r\nMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMV\r\nTWljcm9zb2Z0IENvcnBvcmF0aW9uMRYwFAYDVQQDDA0qLmhvdG1haWwuY29tMIIB\r\nIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAumSiBWrzHZf6WFP5a/j4+K7D\r\n1izLoYKj5Omll0pdxKvKcBRDf+iaIkCbSOPNpx2uWGZdwNwkabYCQavaBf2ebwmS\r\nS8i1CJpHflO+k0qYd5WUi7sSsZ3+6RaCMdLoDIPGyYMQuy7TFtVO7LSt5+qscyyi\r\nET8c3lE2aj/XW13UZvRrV65ZJvMjUtwaDnIcAxGeasYoebLsKdqHQ2uTr4PmNwCc\r\nviGVFSOzkGAoC0PfyqKB2xUWy3Kc5zRI2xvUW8Jb2b/9Ze3g55pIUzKsjpglkQTm\r\nedVPSYYPGNz6Kl/ZshBXdBAk398q1JkSmUaTMa2hJgBbcC+73ax40AJDGJlz+QID\r\nAQABo4IB6zCCAecwDgYDVR0PAQH/BAQDAgWgMEkGA1UdIARCMEAwPgYGZ4EMAQIC\r\nMDQwMgYIKwYBBQUHAgEWJmh0dHBzOi8vd3d3Lmdsb2JhbHNpZ24uY29tL3JlcG9z\r\naXRvcnkvMEAGA1UdEQQ5MDeCDSouaG90bWFpbC5jb22CCioubGl2ZS5jb22CDSou\r\nb3V0bG9vay5jb22CC2hvdG1haWwuY29tMAkGA1UdEwQCMAAwHQYDVR0lBBYwFAYI\r\nKwYBBQUHAwEGCCsGAQUFBwMCMEUGA1UdHwQ+MDwwOqA4oDaGNGh0dHA6Ly9jcmwu\r\nZ2xvYmFsc2lnbi5jb20vZ3MvZ3Nvcmdhbml6YXRpb252YWxnMi5jcmwwgZYGCCsG\r\nAQUFBwEBBIGJMIGGMEcGCCsGAQUFBzAChjtodHRwOi8vc2VjdXJlLmdsb2JhbHNp\r\nZ24uY29tL2NhY2VydC9nc29yZ2FuaXphdGlvbnZhbGcyLmNydDA7BggrBgEFBQcw\r\nAYYvaHR0cDovL29jc3AyLmdsb2JhbHNpZ24uY29tL2dzb3JnYW5pemF0aW9udmFs\r\nZzIwHQYDVR0OBBYEFHbgHqTLsXDt7uMRyE62rnDEfLn9MB8GA1UdIwQYMBaAFF1G\r\nso3ES3Qcu+31c7Y6tziPdZ5+MA0GCSqGSIb3DQEBBQUAA4IBAQByy1+3N6ZRVooI\r\nxqw8Ng+UFz0g7UHkbPEnvTu1uxJ2AojFuP/P1PAk+/6uMRvpPlWg/5uqmOIWxKxJ\r\nLo6xSbkDf4LN+KYwes3XSuPyziZ4QbPnehHhZ0377iiA8fpRJADg9NWKCRHh5aAd\r\ne9QvJUW/GgYkBN+F4yYc2jIjR3Rehv4JYOKS3iXO9OoHsDS2CcCFaS2imgQVfYLg\r\nslBwT/A08PCOhW5huiluSmih7x5Qf7sFDv8jineu6ehKzi8pKnOq4k8G4QiWn38Y\r\nCeiBkkwFOwj7T3M/ITiiSS9DHDGeokj16eBi83Zx3YYiJ9YZvnQ+4GvqJ5eJJ6pR\r\nKKvemr+m\r\n-----END CERTIFICATE-----',
pinned: true,
ignoreTLS: false
}
smtp: {
host: 'smtp.mail.yahoo.com',
port: 465,
secure: true,
ca: '-----BEGIN CERTIFICATE-----\r\nMIIHITCCBgmgAwIBAgIQBlVSvxlwsqw8Kc8eVV5EKTANBgkqhkiG9w0BAQUFADBm\r\nMQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3\r\nd3cuZGlnaWNlcnQuY29tMSUwIwYDVQQDExxEaWdpQ2VydCBIaWdoIEFzc3VyYW5j\r\nZSBDQS0zMB4XDTE0MDYwNTAwMDAwMFoXDTE1MDYyMzEyMDAwMFowYjELMAkGA1UE\r\nBhMCVVMxCzAJBgNVBAgTAkNBMRIwEAYDVQQHEwlTdW5ueXZhbGUxFDASBgNVBAoM\r\nC1lhaG9vISBJbmMuMRwwGgYDVQQDExNzbXRwLm1haWwueWFob28uY29tMIIBIjAN\r\nBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA339OSxTIuW6NVqKwYHFBbg4/tmEx\r\nMYpzohtObPaWgvLTsYdce+WlQFBU5rxaCMUtVsVAGJWs5uQRDVHXRLPnOWe+foqZ\r\n5ItgjYIQc1XeMv6BZEeaF3Bum5ehUyLB3y48cjbWxma1QlkZ8XAK0f9AS7ySWAen\r\no5OXJlMFAqXGtKMAhY46dzSY0wjgdrvgiomFRy0iQKV1DxGsXoXSMEszlUTQoNQ3\r\nWTsiA3O//cdWE06wWeA3/90cb7QkU3KflSoyfi878BQGpPR1L+rLNsqnc8QuucbD\r\nz4Q++rxGqgg4QYrOtmZfAn96TXhPWCwKld6FN+f3uV5ITOBFx44M5v1ILQIDAQAB\r\no4IDzTCCA8kwHwYDVR0jBBgwFoAUUOpzidsp+xCPnuUBINTeeZlIg/cwHQYDVR0O\r\nBBYEFEhOpSFQjO/QrSNfVwyInZUhyzhsMIICJAYDVR0RBIICGzCCAheCE3NtdHAu\r\nbWFpbC55YWhvby5jb22CFnNtdHAubWFpbC55YWhvby5jb20uYXKCFnNtdHAubWFp\r\nbC55YWhvby5jb20uYXWCFnNtdHAubWFpbC55YWhvby5jb20uYnKCFnNtdHAubWFp\r\nbC55YWhvby5jb20uY26CFnNtdHAubWFpbC55YWhvby5jb20uaGuCFnNtdHAubWFp\r\nbC55YWhvby5jb20ubXmCFnNtdHAubWFpbC55YWhvby5jb20ucGiCFnNtdHAubWFp\r\nbC55YWhvby5jb20uc2eCFnNtdHAubWFpbC55YWhvby5jb20udHeCFnNtdHAubWFp\r\nbC55YWhvby5jb20udm6CFXNtdHAubWFpbC55YWhvby5jby5pZIIVc210cC5tYWls\r\nLnlhaG9vLmNvLmlughVzbXRwLm1haWwueWFob28uY28ua3KCFXNtdHAubWFpbC55\r\nYWhvby5jby50aIIVc210cC5tYWlsLnlhaG9vLmNvLnVrghJzbXRwLm1haWwueWFo\r\nb28uY2GCEnNtdHAubWFpbC55YWhvby5jboISc210cC5tYWlsLnlhaG9vLmRlghJz\r\nbXRwLm1haWwueWFob28uZXOCEnNtdHAubWFpbC55YWhvby5mcoISc210cC5tYWls\r\nLnlhaG9vLml0gg9zbXRwLnk3bWFpbC5jb22CFHNtdHAuY29ycmVvLnlhaG9vLmVz\r\nMA4GA1UdDwEB/wQEAwIFoDAdBgNVHSUEFjAUBggrBgEFBQcDAQYIKwYBBQUHAwIw\r\nYQYDVR0fBFowWDAqoCigJoYkaHR0cDovL2NybDMuZGlnaWNlcnQuY29tL2NhMy1n\r\nMjguY3JsMCqgKKAmhiRodHRwOi8vY3JsNC5kaWdpY2VydC5jb20vY2EzLWcyOC5j\r\ncmwwQgYDVR0gBDswOTA3BglghkgBhv1sAQEwKjAoBggrBgEFBQcCARYcaHR0cHM6\r\nLy93d3cuZGlnaWNlcnQuY29tL0NQUzB7BggrBgEFBQcBAQRvMG0wJAYIKwYBBQUH\r\nMAGGGGh0dHA6Ly9vY3NwLmRpZ2ljZXJ0LmNvbTBFBggrBgEFBQcwAoY5aHR0cDov\r\nL2NhY2VydHMuZGlnaWNlcnQuY29tL0RpZ2lDZXJ0SGlnaEFzc3VyYW5jZUNBLTMu\r\nY3J0MAwGA1UdEwEB/wQCMAAwDQYJKoZIhvcNAQEFBQADggEBABshjUND4hADHGfe\r\ncV9XGXcr9h3u7mT5kgdcgGFhcrFAlwkzt0NUCbuN0X8DWHE72Tpb3zRE25CmUUJe\r\nktBbq9PQb4b5/Wt1htAEw0qUs3BsUbqejK9OHJ/11Jn2ek4+SLJuYlijzc7KM3F/\r\nyz7ZTZtKR0PglkXfqbpvWYGabYpfL2FRLoJ7alTLsMJcFxbSLYcAIMxufj7RyTBJ\r\nbKgRJl4wmP4+Zc2Q1p59mENY0u5HqVAAOmWc0jNb0/31+tRr5f6EgXxK++7TQOpF\r\n0TOaFsXlzRlpKfmIbzVr2nfwghV5/bRZj96TK3g1OoOz4C8ksK4INHnUdTAqZ18M\r\nvHpnJw4=\r\n-----END CERTIFICATE-----',
pinned: true,
ignoreTLS: false
}
},
tonline: {
imap: {
host: 'secureimap.t-online.de',
port: 993,
secure: true,
ca: '-----BEGIN CERTIFICATE-----\r\nMIIGmzCCBYOgAwIBAgIIIbZ3foy9DqgwDQYJKoZIhvcNAQEFBQAwcTELMAkGA1UE\r\nBhMCREUxHDAaBgNVBAoTE0RldXRzY2hlIFRlbGVrb20gQUcxHzAdBgNVBAsTFlQt\r\nVGVsZVNlYyBUcnVzdCBDZW50ZXIxIzAhBgNVBAMTGkRldXRzY2hlIFRlbGVrb20g\r\nUm9vdCBDQSAyMB4XDTEzMDMwMTEzNTgyOVoXDTE5MDcwOTIzNTkwMFowgckxCzAJ\r\nBgNVBAYTAkRFMSUwIwYDVQQKExxULVN5c3RlbXMgSW50ZXJuYXRpb25hbCBHbWJI\r\nMR8wHQYDVQQLExZULVN5c3RlbXMgVHJ1c3QgQ2VudGVyMQwwCgYDVQQIEwNOUlcx\r\nDjAMBgNVBBETBTU3MjUwMRAwDgYDVQQHEwdOZXRwaGVuMSAwHgYDVQQJExdVbnRl\r\ncmUgSW5kdXN0cmllc3RyLiAyMDEgMB4GA1UEAxMXVGVsZVNlYyBTZXJ2ZXJQYXNz\r\nIERFLTEwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCwg+9QiuYAxX9/\r\n4F9XRZrS1o0q+aa9L/5/K3vc+RqPpliiZ24vTkJc1JDpXrbWXS25uT3yzHukQrhI\r\nq0AbcRqNEAeFi5EhUiEM/vtb4BYGHdqfXQ3etgYYcCtJ43NAHaSgsyQ9kyGV2lmM\r\nwkeAX3qZ2CGE9/cR6w+bOogHArBdk2JaHG09myNZDytr6oUbWLjLd/qhC9YzyZSX\r\nbZgE/kh5L5Y6P9paw2pDdn7+Ni4pXzlmoj1k43uiz+h2ibe3DO9dKMZAaEKeyG1O\r\ng0f0r53M8O+8Bm2sXtWelrAgrfFlISgWzO1hkNs12rWpr4c5Ygde/behx9OQmPwp\r\nmS+e3WvTAgMBAAGjggLcMIIC2DAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYEFGJP\r\nE842Z4TNGfygTxmL7xVUATIcMB8GA1UdIwQYMBaAFDHDeRu69VPXF+CJei0XbAqz\r\nK50zMBIGA1UdEwEB/wQIMAYBAf8CAQAwWQYDVR0gBFIwUDBEBgkrBgEEAb1HDQIw\r\nNzA1BggrBgEFBQcCARYpaHR0cDovL3d3dy50ZWxlc2VjLmRlL3NlcnZlcnBhc3Mv\r\nY3BzLmh0bWwwCAYGZ4EMAQICMIHvBgNVHR8EgecwgeQwOqA4oDaGNGh0dHA6Ly9j\r\ncmwuc2VydmVycGFzcy50ZWxlc2VjLmRlL3JsL0RUX1JPT1RfQ0FfMi5jcmwwgaWg\r\ngaKggZ+GgZxsZGFwOi8vbGRhcC5zZXJ2ZXJwYXNzLnRlbGVzZWMuZGUvQ049RGV1\r\ndHNjaGUlMjBUZWxla29tJTIwUm9vdCUyMENBJTIwMixPVT1ULVRlbGVTZWMlMjBU\r\ncnVzdCUyMENlbnRlcixPPURldXRzY2hlJTIwVGVsZWtvbSUyMEFHLEM9REU/QXV0\r\naG9yaXR5UmV2b2NhdGlvbkxpc3QwggEjBggrBgEFBQcBAQSCARUwggERMCoGCCsG\r\nAQUFBzABhh5odHRwOi8vb2NzcDAyLnRlbGVzZWMuZGUvb2NzcHIwQQYIKwYBBQUH\r\nMAKGNWh0dHA6Ly9jcmwuc2VydmVycGFzcy50ZWxlc2VjLmRlL2NydC9EVF9ST09U\r\nX0NBXzIuY2VyMIGfBggrBgEFBQcwAoaBkmxkYXA6Ly9sZGFwLnNlcnZlcnBhc3Mu\r\ndGVsZXNlYy5kZS9DTj1EZXV0c2NoZSUyMFRlbGVrb20lMjBSb290JTIwQ0ElMjAy\r\nLE9VPVQtVGVsZVNlYyUyMFRydXN0JTIwQ2VudGVyLE89RGV1dHNjaGUlMjBUZWxl\r\na29tJTIwQUcsQz1ERT9jQUNlcnRpZmljYXRlMA0GCSqGSIb3DQEBBQUAA4IBAQBO\r\nE04qoEkEc9ad+WwSurVYfcDdjGvpqrtbI89woXDsWLQTMhA7D7jVuls90SJns0vc\r\nK9qoYkEGt0/ZlawLe2lyNWtueHfUf+dgleUunwHYLxuj3jQ2ERzQLVLrswjecRpX\r\nvGAGej89WpGQ9PMq27WGNC5WCmzVC9rk5naFgacsbwKwyjU0LoBArtAQnAAlpHDw\r\nPenv1Pe7MhUkCK0LqdTvkI/AHFzPYg/l5E3j8lQQ8hiKx8U6wf9xVKECLA2RlRqY\r\nUX2rpjQNxnvEq/mEQv3x3mLOEFJ3TAKI+soDgOOi0OG8+ywhm6S+7Z9lTlJ+BcD6\r\noy1MNKd4CQbltHLMTFUH\r\n-----END CERTIFICATE-----',
pinned: true,
ignoreTLS: false
},
gmx: {
imap: {
host: 'imap.gmx.net',
port: 993,
secure: true,
ca: '-----BEGIN CERTIFICATE-----\r\nMIIHUDCCBjigAwIBAgIIM52e2Oc5J8kwDQYJKoZIhvcNAQEFBQAwgckxCzAJBgNV\r\nBAYTAkRFMSUwIwYDVQQKExxULVN5c3RlbXMgSW50ZXJuYXRpb25hbCBHbWJIMR8w\r\nHQYDVQQLExZULVN5c3RlbXMgVHJ1c3QgQ2VudGVyMQwwCgYDVQQIEwNOUlcxDjAM\r\nBgNVBBETBTU3MjUwMRAwDgYDVQQHEwdOZXRwaGVuMSAwHgYDVQQJExdVbnRlcmUg\r\nSW5kdXN0cmllc3RyLiAyMDEgMB4GA1UEAxMXVGVsZVNlYyBTZXJ2ZXJQYXNzIERF\r\nLTEwHhcNMTMxMTEyMTAxNzMxWhcNMTYxMTE3MjM1OTU5WjCBnTELMAkGA1UEBhMC\r\nREUxHjAcBgNVBAoMFTEmMSBNYWlsICYgTWVkaWEgR21iSDEdMBsGA1UECBMUUmhp\r\nbmVsYW5kLVBhbGF0aW5hdGUxEjAQBgNVBAcTCU1vbnRhYmF1cjEkMCIGCSqGSIb3\r\nDQEJARYVc2VydmVyLWNlcnRzQDF1bmQxLmRlMRUwEwYDVQQDEwxpbWFwLmdteC5u\r\nZXQwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDHP3QN1ztX5m8LaC9m\r\nt2nrcLBEfxb61FQCieXW4OV/D5HEy7vgQIAeS+pg2/9ClawprQFbLUa46owUcJuq\r\no2SaefsYqjRhIP/tPeyGRJQmfnyCRRoHIYno57/bz2qmHjzvkqEUMvNGVVej7BJv\r\nGukNKPXYicodkRodF3EicTPnGitGeXOsAxyPuCPIsBPAMuuuBzKenS7nj+yseSGe\r\nkM7/kjtNgbjDyBXi2BvoUS/U+Gm9p1lq0ymT9fhGj/k9/Qz312GHYxfZB1bhL0pk\r\nadF25P8fTIy5tKMWqJfgCF+eaOwgkYecxeCcrt2E3D8ThLvmkBRrXsTT51ZzD7sp\r\n7hPpAgMBAAGjggNkMIIDYDAfBgNVHSMEGDAWgBRiTxPONmeEzRn8oE8Zi+8VVAEy\r\nHDAOBgNVHQ8BAf8EBAMCBaAwHQYDVR0lBBYwFAYIKwYBBQUHAwIGCCsGAQUFBwMB\r\nMB0GA1UdDgQWBBRfb2gYyD6V+oBN2Ii5WQj0AddemjBZBgNVHSAEUjBQMEQGCSsG\r\nAQQBvUcNAjA3MDUGCCsGAQUFBwIBFilodHRwOi8vd3d3LnRlbGVzZWMuZGUvc2Vy\r\ndmVycGFzcy9jcHMuaHRtbDAIBgZngQwBAgIwggEhBgNVHR8EggEYMIIBFDBFoEOg\r\nQYY/aHR0cDovL2NybC5zZXJ2ZXJwYXNzLnRlbGVzZWMuZGUvcmwvVGVsZVNlY19T\r\nZXJ2ZXJQYXNzX0RFLTEuY3JsMIHKoIHHoIHEhoHBbGRhcDovL2xkYXAuc2VydmVy\r\ncGFzcy50ZWxlc2VjLmRlL2NuPVRlbGVTZWMlMjBTZXJ2ZXJQYXNzJTIwREUtMSxv\r\ndT1ULVN5c3RlbXMlMjBUcnVzdCUyMENlbnRlcixvPVQtU3lzdGVtcyUyMEludGVy\r\nbmF0aW9uYWwlMjBHbWJILGM9ZGU/Y2VydGlmaWNhdGVSZXZvY2F0aW9ubGlzdD9i\r\nYXNlP2NlcnRpZmljYXRlUmV2b2NhdGlvbmxpc3Q9KjCCATkGCCsGAQUFBwEBBIIB\r\nKzCCAScwMwYIKwYBBQUHMAGGJ2h0dHA6Ly9vY3NwLnNlcnZlcnBhc3MudGVsZXNl\r\nYy5kZS9vY3NwcjBMBggrBgEFBQcwAoZAaHR0cDovL2NybC5zZXJ2ZXJwYXNzLnRl\r\nbGVzZWMuZGUvY3J0L1RlbGVTZWNfU2VydmVyUGFzc19ERS0xLmNlcjCBoQYIKwYB\r\nBQUHMAKGgZRsZGFwOi8vbGRhcC5zZXJ2ZXJwYXNzLnRlbGVzZWMuZGUvY249VGVs\r\nZVNlYyUyMFNlcnZlclBhc3MlMjBERS0xLG91PVQtU3lzdGVtcyUyMFRydXN0JTIw\r\nQ2VudGVyLG89VC1TeXN0ZW1zJTIwSW50ZXJuYXRpb25hbCUyMEdtYkgsYz1kZT9j\r\nQUNlcnRpZmljYXRlMAwGA1UdEwEB/wQCMAAwJAYDVR0RBB0wG4IMaW1hcC5nbXgu\r\nbmV0ggtpbWFwLmdteC5kZTANBgkqhkiG9w0BAQUFAAOCAQEADl442s2ouynDNEzl\r\nBPzhCxjp47TmKzgzrCGh1AH3+UOkw54mYU65UaY9cJ51jgOu+pjNB4KxVfcqyG5O\r\nJcowS09ZtZ+khb1OkzXAAbEqNj0oyH/1K550d5Ir5VbXF0ZdVKVTzEFRyjvUt5NJ\r\n/b6Q2bbY/sPm8QiK9SPaYkO5/3J1KB0u7PkqqfOhZ9UJE6mIN66T2bpFmAwle3zL\r\n6+L/VwYSgUNs/w2l6xWlYP3pFFaT12TO43Q057I3vP7yCt3QE/VLDpdQOQ3H0oy2\r\nJQGPJLFby9MPYNkmEmwjh4e95TAY4ZkZMe08ix0J1Smy8DwjTz6SXqYC+EC/PQlh\r\nE0UHxA==\r\n-----END CERTIFICATE-----',
pinned: true,
ignoreTLS: false
},
smtp: {
host: 'mail.gmx.net',
port: 587,
secure: false,
ca: '-----BEGIN CERTIFICATE-----\r\nMIIHbDCCBlSgAwIBAgIJAJ74Ek9UaA0TMA0GCSqGSIb3DQEBBQUAMIHJMQswCQYD\r\nVQQGEwJERTElMCMGA1UEChMcVC1TeXN0ZW1zIEludGVybmF0aW9uYWwgR21iSDEf\r\nMB0GA1UECxMWVC1TeXN0ZW1zIFRydXN0IENlbnRlcjEMMAoGA1UECBMDTlJXMQ4w\r\nDAYDVQQREwU1NzI1MDEQMA4GA1UEBxMHTmV0cGhlbjEgMB4GA1UECRMXVW50ZXJl\r\nIEluZHVzdHJpZXN0ci4gMjAxIDAeBgNVBAMTF1RlbGVTZWMgU2VydmVyUGFzcyBE\r\nRS0xMB4XDTEzMTExMjEwMTY0N1oXDTE2MTExNzIzNTk1OVowgZ0xCzAJBgNVBAYT\r\nAkRFMR4wHAYDVQQKDBUxJjEgTWFpbCAmIE1lZGlhIEdtYkgxHTAbBgNVBAgTFFJo\r\naW5lbGFuZC1QYWxhdGluYXRlMRIwEAYDVQQHEwlNb250YWJhdXIxJDAiBgkqhkiG\r\n9w0BCQEWFXNlcnZlci1jZXJ0c0AxdW5kMS5kZTEVMBMGA1UEAxMMbWFpbC5nbXgu\r\nbmV0MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAsEUYppbJXUpQELIL\r\nETAFUwreDfSmbyi9gJPUqquogROHEoRV+bBFZ2YCiHgLU8AggcLmM74HAQMFt8F5\r\neS2OyuEar3E3tNW5XFiI1QblTtY3B7o1coKyq/i+tV3b1jGq4+1aJeeD3UpcraBm\r\nk2XuQgQ6WAeY+XCYyI/OzZ56ZjQ17LHMMI0ayu7SSV+VBpG9loC9E5A82iVtZsqo\r\nSaUZpZf/xICpUgNJ+RlalSsoE6FdCywE/gEEmkpAUh+Mv0WvHITk6aqtMFWex8tE\r\n8cwJGQbmJGt9x8BExbhMY6uQ+HsnsDCPCMVh5JGKx7/e+qyxtHYGChOeI16m2+MT\r\nz47jSQIDAQABo4IDfzCCA3swHwYDVR0jBBgwFoAUYk8TzjZnhM0Z/KBPGYvvFVQB\r\nMhwwDgYDVR0PAQH/BAQDAgWgMB0GA1UdJQQWMBQGCCsGAQUFBwMCBggrBgEFBQcD\r\nATAdBgNVHQ4EFgQUH8mLfxsd5FEh9M+3t3rP6qLiFTEwWQYDVR0gBFIwUDBEBgkr\r\nBgEEAb1HDQIwNzA1BggrBgEFBQcCARYpaHR0cDovL3d3dy50ZWxlc2VjLmRlL3Nl\r\ncnZlcnBhc3MvY3BzLmh0bWwwCAYGZ4EMAQICMIIBIQYDVR0fBIIBGDCCARQwRaBD\r\noEGGP2h0dHA6Ly9jcmwuc2VydmVycGFzcy50ZWxlc2VjLmRlL3JsL1RlbGVTZWNf\r\nU2VydmVyUGFzc19ERS0xLmNybDCByqCBx6CBxIaBwWxkYXA6Ly9sZGFwLnNlcnZl\r\ncnBhc3MudGVsZXNlYy5kZS9jbj1UZWxlU2VjJTIwU2VydmVyUGFzcyUyMERFLTEs\r\nb3U9VC1TeXN0ZW1zJTIwVHJ1c3QlMjBDZW50ZXIsbz1ULVN5c3RlbXMlMjBJbnRl\r\ncm5hdGlvbmFsJTIwR21iSCxjPWRlP2NlcnRpZmljYXRlUmV2b2NhdGlvbmxpc3Q/\r\nYmFzZT9jZXJ0aWZpY2F0ZVJldm9jYXRpb25saXN0PSowggE5BggrBgEFBQcBAQSC\r\nASswggEnMDMGCCsGAQUFBzABhidodHRwOi8vb2NzcC5zZXJ2ZXJwYXNzLnRlbGVz\r\nZWMuZGUvb2NzcHIwTAYIKwYBBQUHMAKGQGh0dHA6Ly9jcmwuc2VydmVycGFzcy50\r\nZWxlc2VjLmRlL2NydC9UZWxlU2VjX1NlcnZlclBhc3NfREUtMS5jZXIwgaEGCCsG\r\nAQUFBzAChoGUbGRhcDovL2xkYXAuc2VydmVycGFzcy50ZWxlc2VjLmRlL2NuPVRl\r\nbGVTZWMlMjBTZXJ2ZXJQYXNzJTIwREUtMSxvdT1ULVN5c3RlbXMlMjBUcnVzdCUy\r\nMENlbnRlcixvPVQtU3lzdGVtcyUyMEludGVybmF0aW9uYWwlMjBHbWJILGM9ZGU/\r\nY0FDZXJ0aWZpY2F0ZTAMBgNVHRMBAf8EAjAAMD8GA1UdEQQ4MDaCDG1haWwuZ214\r\nLm5ldIILbWFpbC5nbXguZGWCDHNtdHAuZ214Lm5ldIILc210cC5nbXguZGUwDQYJ\r\nKoZIhvcNAQEFBQADggEBAJ5SKsBXoOTO0ztsFh8RVd/iu6sijEMpKVF+/cs74TZN\r\nK1QWIz8Ay1f3SUPWXTvkmvayUXWGtxZfYoRIrx3feWBdUozJxe0wz1O3tDQOQgRX\r\nnkcwk7nibTUmW2rk5AIwV0jzOHtIbAGjSdc6my0543e7dVkhcuoCOC+g2NmD+pHF\r\ni1KpmqTLecDlFvprIZUJ23AT1uTnuXYg/tFHeDn3ga00Gce82xZQJelRirJs01SR\r\nDGEBaPXBjJVJNoAP/qEy3jQNRWo5TbSgwn3DTR81FJvltmf0c+zlt4fmcpjpr5ni\r\nFvK7L0rZQL5MOGHPpgRIukEBZCowYr3OYpZYBEaB94I=\r\n-----END CERTIFICATE-----',
pinned: true,
ignoreTLS: false
}
smtp: {
host: 'securesmtp.t-online.de',
port: 465,
secure: true,
ca: '-----BEGIN CERTIFICATE-----\r\nMIIGmzCCBYOgAwIBAgIIIbZ3foy9DqgwDQYJKoZIhvcNAQEFBQAwcTELMAkGA1UE\r\nBhMCREUxHDAaBgNVBAoTE0RldXRzY2hlIFRlbGVrb20gQUcxHzAdBgNVBAsTFlQt\r\nVGVsZVNlYyBUcnVzdCBDZW50ZXIxIzAhBgNVBAMTGkRldXRzY2hlIFRlbGVrb20g\r\nUm9vdCBDQSAyMB4XDTEzMDMwMTEzNTgyOVoXDTE5MDcwOTIzNTkwMFowgckxCzAJ\r\nBgNVBAYTAkRFMSUwIwYDVQQKExxULVN5c3RlbXMgSW50ZXJuYXRpb25hbCBHbWJI\r\nMR8wHQYDVQQLExZULVN5c3RlbXMgVHJ1c3QgQ2VudGVyMQwwCgYDVQQIEwNOUlcx\r\nDjAMBgNVBBETBTU3MjUwMRAwDgYDVQQHEwdOZXRwaGVuMSAwHgYDVQQJExdVbnRl\r\ncmUgSW5kdXN0cmllc3RyLiAyMDEgMB4GA1UEAxMXVGVsZVNlYyBTZXJ2ZXJQYXNz\r\nIERFLTEwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCwg+9QiuYAxX9/\r\n4F9XRZrS1o0q+aa9L/5/K3vc+RqPpliiZ24vTkJc1JDpXrbWXS25uT3yzHukQrhI\r\nq0AbcRqNEAeFi5EhUiEM/vtb4BYGHdqfXQ3etgYYcCtJ43NAHaSgsyQ9kyGV2lmM\r\nwkeAX3qZ2CGE9/cR6w+bOogHArBdk2JaHG09myNZDytr6oUbWLjLd/qhC9YzyZSX\r\nbZgE/kh5L5Y6P9paw2pDdn7+Ni4pXzlmoj1k43uiz+h2ibe3DO9dKMZAaEKeyG1O\r\ng0f0r53M8O+8Bm2sXtWelrAgrfFlISgWzO1hkNs12rWpr4c5Ygde/behx9OQmPwp\r\nmS+e3WvTAgMBAAGjggLcMIIC2DAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYEFGJP\r\nE842Z4TNGfygTxmL7xVUATIcMB8GA1UdIwQYMBaAFDHDeRu69VPXF+CJei0XbAqz\r\nK50zMBIGA1UdEwEB/wQIMAYBAf8CAQAwWQYDVR0gBFIwUDBEBgkrBgEEAb1HDQIw\r\nNzA1BggrBgEFBQcCARYpaHR0cDovL3d3dy50ZWxlc2VjLmRlL3NlcnZlcnBhc3Mv\r\nY3BzLmh0bWwwCAYGZ4EMAQICMIHvBgNVHR8EgecwgeQwOqA4oDaGNGh0dHA6Ly9j\r\ncmwuc2VydmVycGFzcy50ZWxlc2VjLmRlL3JsL0RUX1JPT1RfQ0FfMi5jcmwwgaWg\r\ngaKggZ+GgZxsZGFwOi8vbGRhcC5zZXJ2ZXJwYXNzLnRlbGVzZWMuZGUvQ049RGV1\r\ndHNjaGUlMjBUZWxla29tJTIwUm9vdCUyMENBJTIwMixPVT1ULVRlbGVTZWMlMjBU\r\ncnVzdCUyMENlbnRlcixPPURldXRzY2hlJTIwVGVsZWtvbSUyMEFHLEM9REU/QXV0\r\naG9yaXR5UmV2b2NhdGlvbkxpc3QwggEjBggrBgEFBQcBAQSCARUwggERMCoGCCsG\r\nAQUFBzABhh5odHRwOi8vb2NzcDAyLnRlbGVzZWMuZGUvb2NzcHIwQQYIKwYBBQUH\r\nMAKGNWh0dHA6Ly9jcmwuc2VydmVycGFzcy50ZWxlc2VjLmRlL2NydC9EVF9ST09U\r\nX0NBXzIuY2VyMIGfBggrBgEFBQcwAoaBkmxkYXA6Ly9sZGFwLnNlcnZlcnBhc3Mu\r\ndGVsZXNlYy5kZS9DTj1EZXV0c2NoZSUyMFRlbGVrb20lMjBSb290JTIwQ0ElMjAy\r\nLE9VPVQtVGVsZVNlYyUyMFRydXN0JTIwQ2VudGVyLE89RGV1dHNjaGUlMjBUZWxl\r\na29tJTIwQUcsQz1ERT9jQUNlcnRpZmljYXRlMA0GCSqGSIb3DQEBBQUAA4IBAQBO\r\nE04qoEkEc9ad+WwSurVYfcDdjGvpqrtbI89woXDsWLQTMhA7D7jVuls90SJns0vc\r\nK9qoYkEGt0/ZlawLe2lyNWtueHfUf+dgleUunwHYLxuj3jQ2ERzQLVLrswjecRpX\r\nvGAGej89WpGQ9PMq27WGNC5WCmzVC9rk5naFgacsbwKwyjU0LoBArtAQnAAlpHDw\r\nPenv1Pe7MhUkCK0LqdTvkI/AHFzPYg/l5E3j8lQQ8hiKx8U6wf9xVKECLA2RlRqY\r\nUX2rpjQNxnvEq/mEQv3x3mLOEFJ3TAKI+soDgOOi0OG8+ywhm6S+7Z9lTlJ+BcD6\r\noy1MNKd4CQbltHLMTFUH\r\n-----END CERTIFICATE-----',
pinned: true,
ignoreTLS: false
}
},
outlook: {
imap: {
host: 'imap-mail.outlook.com',
port: 993,
secure: true,
ca: '-----BEGIN CERTIFICATE-----\r\nMIIFQjCCBCqgAwIBAgISESHl0vjrML7zKmGlv42YL75vMA0GCSqGSIb3DQEBBQUA\r\nMF0xCzAJBgNVBAYTAkJFMRkwFwYDVQQKExBHbG9iYWxTaWduIG52LXNhMTMwMQYD\r\nVQQDEypHbG9iYWxTaWduIE9yZ2FuaXphdGlvbiBWYWxpZGF0aW9uIENBIC0gRzIw\r\nHhcNMTMwNDI0MjAzNTA5WhcNMTYwNDI0MjAzNTA5WjBsMQswCQYDVQQGEwJVUzET\r\nMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMV\r\nTWljcm9zb2Z0IENvcnBvcmF0aW9uMRYwFAYDVQQDDA0qLmhvdG1haWwuY29tMIIB\r\nIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAumSiBWrzHZf6WFP5a/j4+K7D\r\n1izLoYKj5Omll0pdxKvKcBRDf+iaIkCbSOPNpx2uWGZdwNwkabYCQavaBf2ebwmS\r\nS8i1CJpHflO+k0qYd5WUi7sSsZ3+6RaCMdLoDIPGyYMQuy7TFtVO7LSt5+qscyyi\r\nET8c3lE2aj/XW13UZvRrV65ZJvMjUtwaDnIcAxGeasYoebLsKdqHQ2uTr4PmNwCc\r\nviGVFSOzkGAoC0PfyqKB2xUWy3Kc5zRI2xvUW8Jb2b/9Ze3g55pIUzKsjpglkQTm\r\nedVPSYYPGNz6Kl/ZshBXdBAk398q1JkSmUaTMa2hJgBbcC+73ax40AJDGJlz+QID\r\nAQABo4IB6zCCAecwDgYDVR0PAQH/BAQDAgWgMEkGA1UdIARCMEAwPgYGZ4EMAQIC\r\nMDQwMgYIKwYBBQUHAgEWJmh0dHBzOi8vd3d3Lmdsb2JhbHNpZ24uY29tL3JlcG9z\r\naXRvcnkvMEAGA1UdEQQ5MDeCDSouaG90bWFpbC5jb22CCioubGl2ZS5jb22CDSou\r\nb3V0bG9vay5jb22CC2hvdG1haWwuY29tMAkGA1UdEwQCMAAwHQYDVR0lBBYwFAYI\r\nKwYBBQUHAwEGCCsGAQUFBwMCMEUGA1UdHwQ+MDwwOqA4oDaGNGh0dHA6Ly9jcmwu\r\nZ2xvYmFsc2lnbi5jb20vZ3MvZ3Nvcmdhbml6YXRpb252YWxnMi5jcmwwgZYGCCsG\r\nAQUFBwEBBIGJMIGGMEcGCCsGAQUFBzAChjtodHRwOi8vc2VjdXJlLmdsb2JhbHNp\r\nZ24uY29tL2NhY2VydC9nc29yZ2FuaXphdGlvbnZhbGcyLmNydDA7BggrBgEFBQcw\r\nAYYvaHR0cDovL29jc3AyLmdsb2JhbHNpZ24uY29tL2dzb3JnYW5pemF0aW9udmFs\r\nZzIwHQYDVR0OBBYEFHbgHqTLsXDt7uMRyE62rnDEfLn9MB8GA1UdIwQYMBaAFF1G\r\nso3ES3Qcu+31c7Y6tziPdZ5+MA0GCSqGSIb3DQEBBQUAA4IBAQByy1+3N6ZRVooI\r\nxqw8Ng+UFz0g7UHkbPEnvTu1uxJ2AojFuP/P1PAk+/6uMRvpPlWg/5uqmOIWxKxJ\r\nLo6xSbkDf4LN+KYwes3XSuPyziZ4QbPnehHhZ0377iiA8fpRJADg9NWKCRHh5aAd\r\ne9QvJUW/GgYkBN+F4yYc2jIjR3Rehv4JYOKS3iXO9OoHsDS2CcCFaS2imgQVfYLg\r\nslBwT/A08PCOhW5huiluSmih7x5Qf7sFDv8jineu6ehKzi8pKnOq4k8G4QiWn38Y\r\nCeiBkkwFOwj7T3M/ITiiSS9DHDGeokj16eBi83Zx3YYiJ9YZvnQ+4GvqJ5eJJ6pR\r\nKKvemr+m\r\n-----END CERTIFICATE-----',
pinned: true,
ignoreTLS: false
},
webde: {
imap: {
host: 'imap.web.de',
port: 993,
secure: true,
ca: '-----BEGIN CERTIFICATE-----\r\nMIIHUjCCBjqgAwIBAgIIHvqUOScyxYUwDQYJKoZIhvcNAQEFBQAwgckxCzAJBgNV\r\nBAYTAkRFMSUwIwYDVQQKExxULVN5c3RlbXMgSW50ZXJuYXRpb25hbCBHbWJIMR8w\r\nHQYDVQQLExZULVN5c3RlbXMgVHJ1c3QgQ2VudGVyMQwwCgYDVQQIEwNOUlcxDjAM\r\nBgNVBBETBTU3MjUwMRAwDgYDVQQHEwdOZXRwaGVuMSAwHgYDVQQJExdVbnRlcmUg\r\nSW5kdXN0cmllc3RyLiAyMDEgMB4GA1UEAxMXVGVsZVNlYyBTZXJ2ZXJQYXNzIERF\r\nLTEwHhcNMTMwODIxMDgzMTE2WhcNMTYwODI2MjM1OTU5WjCBrTELMAkGA1UEBhMC\r\nREUxHjAcBgNVBAoMFTEmMSBNYWlsICYgTWVkaWEgR21iSDEPMA0GA1UECxMGV0VC\r\nLkRFMR0wGwYDVQQIExRSaGluZWxhbmQtUGFsYXRpbmF0ZTESMBAGA1UEBxMJTW9u\r\ndGFiYXVyMSQwIgYJKoZIhvcNAQkBFhVzZXJ2ZXItY2VydHNAMXVuZDEuZGUxFDAS\r\nBgNVBAMTC2ltYXAud2ViLmRlMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKC\r\nAQEA5dvpcnYczFs9GANTYN5N3mP6ATFVm7P4nYhRIQMj/YssSjmbrOPIEbe12JNC\r\nRO9CI7Z4Dnk42BM5TiH+QKGpcfnitofOv4gKPjwCMcPDHfY152/+YDaiyU/md2Hg\r\n/WrZ/50KwC8Sw1tZkYDXWB0zeJkfPdS2r4ATNrpMR0fYcG08+elz5T2SWNg+c1xL\r\nKFdCh33wZLkijOfW0HA164QjXaLPBjxa+GyZFY19ywOQ85KdFVVLmGUrLz8n4ZLc\r\n7K6KUuzYttnUuVxctFauQ4DRHE/CfUvvNHHgn5d+A2XS7jNUgmUb0gjzy3OtlTP3\r\ngM4ostVufmnlS7qqFDR7A3v4lQIDAQABo4IDVjCCA1IwHwYDVR0jBBgwFoAUYk8T\r\nzjZnhM0Z/KBPGYvvFVQBMhwwDgYDVR0PAQH/BAQDAgWgMB0GA1UdJQQWMBQGCCsG\r\nAQUFBwMCBggrBgEFBQcDATAdBgNVHQ4EFgQUDH66P/Y4NlKGphPniIzmutEI9ocw\r\nWQYDVR0gBFIwUDBEBgkrBgEEAb1HDQIwNzA1BggrBgEFBQcCARYpaHR0cDovL3d3\r\ndy50ZWxlc2VjLmRlL3NlcnZlcnBhc3MvY3BzLmh0bWwwCAYGZ4EMAQICMIIBIQYD\r\nVR0fBIIBGDCCARQwRaBDoEGGP2h0dHA6Ly9jcmwuc2VydmVycGFzcy50ZWxlc2Vj\r\nLmRlL3JsL1RlbGVTZWNfU2VydmVyUGFzc19ERS0xLmNybDCByqCBx6CBxIaBwWxk\r\nYXA6Ly9sZGFwLnNlcnZlcnBhc3MudGVsZXNlYy5kZS9jbj1UZWxlU2VjJTIwU2Vy\r\ndmVyUGFzcyUyMERFLTEsb3U9VC1TeXN0ZW1zJTIwVHJ1c3QlMjBDZW50ZXIsbz1U\r\nLVN5c3RlbXMlMjBJbnRlcm5hdGlvbmFsJTIwR21iSCxjPWRlP2NlcnRpZmljYXRl\r\nUmV2b2NhdGlvbmxpc3Q/YmFzZT9jZXJ0aWZpY2F0ZVJldm9jYXRpb25saXN0PSow\r\nggE5BggrBgEFBQcBAQSCASswggEnMDMGCCsGAQUFBzABhidodHRwOi8vb2NzcC5z\r\nZXJ2ZXJwYXNzLnRlbGVzZWMuZGUvb2NzcHIwTAYIKwYBBQUHMAKGQGh0dHA6Ly9j\r\ncmwuc2VydmVycGFzcy50ZWxlc2VjLmRlL2NydC9UZWxlU2VjX1NlcnZlclBhc3Nf\r\nREUtMS5jZXIwgaEGCCsGAQUFBzAChoGUbGRhcDovL2xkYXAuc2VydmVycGFzcy50\r\nZWxlc2VjLmRlL2NuPVRlbGVTZWMlMjBTZXJ2ZXJQYXNzJTIwREUtMSxvdT1ULVN5\r\nc3RlbXMlMjBUcnVzdCUyMENlbnRlcixvPVQtU3lzdGVtcyUyMEludGVybmF0aW9u\r\nYWwlMjBHbWJILGM9ZGU/Y0FDZXJ0aWZpY2F0ZTAMBgNVHRMBAf8EAjAAMBYGA1Ud\r\nEQQPMA2CC2ltYXAud2ViLmRlMA0GCSqGSIb3DQEBBQUAA4IBAQAp7MQjjOWQ0M7N\r\n21GrPDfWSMR3eJnuMs37I9G2t9i99w7xKtmoVBPyYMORL1zRHn/DEguo4j5ua7CH\r\nrKLh9Sd1wDKqVWGFxvmP4f/mvEx5YVI68mg+M2VQf/h58IXTTZRbUcsv3HVeruI6\r\npgQNUsEQqRVJmrgT/iPd98RhhzBqef6Wfrt3Ns6N835egphxUVcVj/v/PqBCZKQb\r\nNl5QsoaHDqh3XC+Og2awOGvWHUzxUKEqkP5nsMs2YjfOtcQRtxdislQAfRktuUBA\r\nWMZAlvoVF/CIyFvcUH11KNur7HfK5PptvtdvLQaF2c+lN+LGP6D+nbTLqAo1v09o\r\nnZpHbQOt\r\n-----END CERTIFICATE-----',
pinned: true,
ignoreTLS: false
},
smtp: {
host: 'smtp.web.de',
port: 587,
secure: false,
ca: '-----BEGIN CERTIFICATE-----\r\nMIIHUzCCBjugAwIBAgIJAKaEwtMgQ2s5MA0GCSqGSIb3DQEBBQUAMIHJMQswCQYD\r\nVQQGEwJERTElMCMGA1UEChMcVC1TeXN0ZW1zIEludGVybmF0aW9uYWwgR21iSDEf\r\nMB0GA1UECxMWVC1TeXN0ZW1zIFRydXN0IENlbnRlcjEMMAoGA1UECBMDTlJXMQ4w\r\nDAYDVQQREwU1NzI1MDEQMA4GA1UEBxMHTmV0cGhlbjEgMB4GA1UECRMXVW50ZXJl\r\nIEluZHVzdHJpZXN0ci4gMjAxIDAeBgNVBAMTF1RlbGVTZWMgU2VydmVyUGFzcyBE\r\nRS0xMB4XDTEzMDgyMTA4MzU1MVoXDTE2MDgyNjIzNTk1OVowga0xCzAJBgNVBAYT\r\nAkRFMR4wHAYDVQQKDBUxJjEgTWFpbCAmIE1lZGlhIEdtYkgxDzANBgNVBAsTBldF\r\nQi5ERTEdMBsGA1UECBMUUmhpbmVsYW5kLVBhbGF0aW5hdGUxEjAQBgNVBAcTCU1v\r\nbnRhYmF1cjEkMCIGCSqGSIb3DQEJARYVc2VydmVyLWNlcnRzQDF1bmQxLmRlMRQw\r\nEgYDVQQDEwtzbXRwLndlYi5kZTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoC\r\nggEBAL1WfyFqmifaEr6h9yntJKs33fnEVu0pQqS5RV9rnQU0E/cJYJ9RJOoTjhpE\r\nkT1LW8DyTM5vUzpgtFHQM0BOO6jOeZXUYNUaopK3yO4l/cpiitigtk9ZnWfKzbU3\r\n9hlhf+YwUUvoqjUA8I6fSu+VNPNlOBvamo18oHljXAPltL9oiwAxvTLNktBMy4T9\r\njxU1DeGoPbJKGwV7zIBQ2qUHuLkMvy5/H39t79Tih+zfzgfY/xIUfdmPNY9dK8ZY\r\nAtyF/RiUS199pd9dV4Vwh6JLvtNlWchBnKVhragLCGbkjnvUXmS1BPrclLR23s1v\r\nMehgOD2WhXl8PoqrOPb6y+lgMlECAwEAAaOCA1YwggNSMB8GA1UdIwQYMBaAFGJP\r\nE842Z4TNGfygTxmL7xVUATIcMA4GA1UdDwEB/wQEAwIFoDAdBgNVHSUEFjAUBggr\r\nBgEFBQcDAgYIKwYBBQUHAwEwHQYDVR0OBBYEFAUuppozNCv2UzMV5p6CTpkEhsRS\r\nMFkGA1UdIARSMFAwRAYJKwYBBAG9Rw0CMDcwNQYIKwYBBQUHAgEWKWh0dHA6Ly93\r\nd3cudGVsZXNlYy5kZS9zZXJ2ZXJwYXNzL2Nwcy5odG1sMAgGBmeBDAECAjCCASEG\r\nA1UdHwSCARgwggEUMEWgQ6BBhj9odHRwOi8vY3JsLnNlcnZlcnBhc3MudGVsZXNl\r\nYy5kZS9ybC9UZWxlU2VjX1NlcnZlclBhc3NfREUtMS5jcmwwgcqggceggcSGgcFs\r\nZGFwOi8vbGRhcC5zZXJ2ZXJwYXNzLnRlbGVzZWMuZGUvY249VGVsZVNlYyUyMFNl\r\ncnZlclBhc3MlMjBERS0xLG91PVQtU3lzdGVtcyUyMFRydXN0JTIwQ2VudGVyLG89\r\nVC1TeXN0ZW1zJTIwSW50ZXJuYXRpb25hbCUyMEdtYkgsYz1kZT9jZXJ0aWZpY2F0\r\nZVJldm9jYXRpb25saXN0P2Jhc2U/Y2VydGlmaWNhdGVSZXZvY2F0aW9ubGlzdD0q\r\nMIIBOQYIKwYBBQUHAQEEggErMIIBJzAzBggrBgEFBQcwAYYnaHR0cDovL29jc3Au\r\nc2VydmVycGFzcy50ZWxlc2VjLmRlL29jc3ByMEwGCCsGAQUFBzAChkBodHRwOi8v\r\nY3JsLnNlcnZlcnBhc3MudGVsZXNlYy5kZS9jcnQvVGVsZVNlY19TZXJ2ZXJQYXNz\r\nX0RFLTEuY2VyMIGhBggrBgEFBQcwAoaBlGxkYXA6Ly9sZGFwLnNlcnZlcnBhc3Mu\r\ndGVsZXNlYy5kZS9jbj1UZWxlU2VjJTIwU2VydmVyUGFzcyUyMERFLTEsb3U9VC1T\r\neXN0ZW1zJTIwVHJ1c3QlMjBDZW50ZXIsbz1ULVN5c3RlbXMlMjBJbnRlcm5hdGlv\r\nbmFsJTIwR21iSCxjPWRlP2NBQ2VydGlmaWNhdGUwDAYDVR0TAQH/BAIwADAWBgNV\r\nHREEDzANggtzbXRwLndlYi5kZTANBgkqhkiG9w0BAQUFAAOCAQEAGFtJxVxWrURy\r\nFfR4UfmW+N1cZZx9sfC5jolV8LGje87DgbWdqu5TRL9FoQ1pwOTbM9mc3yWSoIbU\r\nx6E+rziJK+SFGMIy+Lt13P9M9Oc8JzVHoVEAgqlEeO6OYxrGE6SaSjZODJxPaEtU\r\nzEAKf0HVJQoGaU6fD+/8l3yhksAlsF/L85nP+KcZtoancOkJWE0GQMZp7pdLU0Ou\r\nFUQoAcqMIyHQfDqJ5iwvx/9C7jmy3Nvw9tXdPrdn2O7ywrnFeJuT2xiorZzg6ezn\r\nUlc/sMvd/LPX0f60pSQr9tkZgU4f8Jvx9EvPUCFTRXlXqkBIhgJhgCadZC+wKB1P\r\neQMS8rksXw==\r\n-----END CERTIFICATE-----',
pinned: true,
ignoreTLS: false
}
smtp: {
host: 'smtp-mail.outlook.com',
port: 587,
secure: false,
ca: '-----BEGIN CERTIFICATE-----\r\nMIIFQjCCBCqgAwIBAgISESHl0vjrML7zKmGlv42YL75vMA0GCSqGSIb3DQEBBQUA\r\nMF0xCzAJBgNVBAYTAkJFMRkwFwYDVQQKExBHbG9iYWxTaWduIG52LXNhMTMwMQYD\r\nVQQDEypHbG9iYWxTaWduIE9yZ2FuaXphdGlvbiBWYWxpZGF0aW9uIENBIC0gRzIw\r\nHhcNMTMwNDI0MjAzNTA5WhcNMTYwNDI0MjAzNTA5WjBsMQswCQYDVQQGEwJVUzET\r\nMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMV\r\nTWljcm9zb2Z0IENvcnBvcmF0aW9uMRYwFAYDVQQDDA0qLmhvdG1haWwuY29tMIIB\r\nIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAumSiBWrzHZf6WFP5a/j4+K7D\r\n1izLoYKj5Omll0pdxKvKcBRDf+iaIkCbSOPNpx2uWGZdwNwkabYCQavaBf2ebwmS\r\nS8i1CJpHflO+k0qYd5WUi7sSsZ3+6RaCMdLoDIPGyYMQuy7TFtVO7LSt5+qscyyi\r\nET8c3lE2aj/XW13UZvRrV65ZJvMjUtwaDnIcAxGeasYoebLsKdqHQ2uTr4PmNwCc\r\nviGVFSOzkGAoC0PfyqKB2xUWy3Kc5zRI2xvUW8Jb2b/9Ze3g55pIUzKsjpglkQTm\r\nedVPSYYPGNz6Kl/ZshBXdBAk398q1JkSmUaTMa2hJgBbcC+73ax40AJDGJlz+QID\r\nAQABo4IB6zCCAecwDgYDVR0PAQH/BAQDAgWgMEkGA1UdIARCMEAwPgYGZ4EMAQIC\r\nMDQwMgYIKwYBBQUHAgEWJmh0dHBzOi8vd3d3Lmdsb2JhbHNpZ24uY29tL3JlcG9z\r\naXRvcnkvMEAGA1UdEQQ5MDeCDSouaG90bWFpbC5jb22CCioubGl2ZS5jb22CDSou\r\nb3V0bG9vay5jb22CC2hvdG1haWwuY29tMAkGA1UdEwQCMAAwHQYDVR0lBBYwFAYI\r\nKwYBBQUHAwEGCCsGAQUFBwMCMEUGA1UdHwQ+MDwwOqA4oDaGNGh0dHA6Ly9jcmwu\r\nZ2xvYmFsc2lnbi5jb20vZ3MvZ3Nvcmdhbml6YXRpb252YWxnMi5jcmwwgZYGCCsG\r\nAQUFBwEBBIGJMIGGMEcGCCsGAQUFBzAChjtodHRwOi8vc2VjdXJlLmdsb2JhbHNp\r\nZ24uY29tL2NhY2VydC9nc29yZ2FuaXphdGlvbnZhbGcyLmNydDA7BggrBgEFBQcw\r\nAYYvaHR0cDovL29jc3AyLmdsb2JhbHNpZ24uY29tL2dzb3JnYW5pemF0aW9udmFs\r\nZzIwHQYDVR0OBBYEFHbgHqTLsXDt7uMRyE62rnDEfLn9MB8GA1UdIwQYMBaAFF1G\r\nso3ES3Qcu+31c7Y6tziPdZ5+MA0GCSqGSIb3DQEBBQUAA4IBAQByy1+3N6ZRVooI\r\nxqw8Ng+UFz0g7UHkbPEnvTu1uxJ2AojFuP/P1PAk+/6uMRvpPlWg/5uqmOIWxKxJ\r\nLo6xSbkDf4LN+KYwes3XSuPyziZ4QbPnehHhZ0377iiA8fpRJADg9NWKCRHh5aAd\r\ne9QvJUW/GgYkBN+F4yYc2jIjR3Rehv4JYOKS3iXO9OoHsDS2CcCFaS2imgQVfYLg\r\nslBwT/A08PCOhW5huiluSmih7x5Qf7sFDv8jineu6ehKzi8pKnOq4k8G4QiWn38Y\r\nCeiBkkwFOwj7T3M/ITiiSS9DHDGeokj16eBi83Zx3YYiJ9YZvnQ+4GvqJ5eJJ6pR\r\nKKvemr+m\r\n-----END CERTIFICATE-----',
pinned: true,
ignoreTLS: false
}
},
gmx: {
imap: {
host: 'imap.gmx.net',
port: 993,
secure: true,
ca: '-----BEGIN CERTIFICATE-----\r\nMIIHUDCCBjigAwIBAgIIM52e2Oc5J8kwDQYJKoZIhvcNAQEFBQAwgckxCzAJBgNV\r\nBAYTAkRFMSUwIwYDVQQKExxULVN5c3RlbXMgSW50ZXJuYXRpb25hbCBHbWJIMR8w\r\nHQYDVQQLExZULVN5c3RlbXMgVHJ1c3QgQ2VudGVyMQwwCgYDVQQIEwNOUlcxDjAM\r\nBgNVBBETBTU3MjUwMRAwDgYDVQQHEwdOZXRwaGVuMSAwHgYDVQQJExdVbnRlcmUg\r\nSW5kdXN0cmllc3RyLiAyMDEgMB4GA1UEAxMXVGVsZVNlYyBTZXJ2ZXJQYXNzIERF\r\nLTEwHhcNMTMxMTEyMTAxNzMxWhcNMTYxMTE3MjM1OTU5WjCBnTELMAkGA1UEBhMC\r\nREUxHjAcBgNVBAoMFTEmMSBNYWlsICYgTWVkaWEgR21iSDEdMBsGA1UECBMUUmhp\r\nbmVsYW5kLVBhbGF0aW5hdGUxEjAQBgNVBAcTCU1vbnRhYmF1cjEkMCIGCSqGSIb3\r\nDQEJARYVc2VydmVyLWNlcnRzQDF1bmQxLmRlMRUwEwYDVQQDEwxpbWFwLmdteC5u\r\nZXQwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDHP3QN1ztX5m8LaC9m\r\nt2nrcLBEfxb61FQCieXW4OV/D5HEy7vgQIAeS+pg2/9ClawprQFbLUa46owUcJuq\r\no2SaefsYqjRhIP/tPeyGRJQmfnyCRRoHIYno57/bz2qmHjzvkqEUMvNGVVej7BJv\r\nGukNKPXYicodkRodF3EicTPnGitGeXOsAxyPuCPIsBPAMuuuBzKenS7nj+yseSGe\r\nkM7/kjtNgbjDyBXi2BvoUS/U+Gm9p1lq0ymT9fhGj/k9/Qz312GHYxfZB1bhL0pk\r\nadF25P8fTIy5tKMWqJfgCF+eaOwgkYecxeCcrt2E3D8ThLvmkBRrXsTT51ZzD7sp\r\n7hPpAgMBAAGjggNkMIIDYDAfBgNVHSMEGDAWgBRiTxPONmeEzRn8oE8Zi+8VVAEy\r\nHDAOBgNVHQ8BAf8EBAMCBaAwHQYDVR0lBBYwFAYIKwYBBQUHAwIGCCsGAQUFBwMB\r\nMB0GA1UdDgQWBBRfb2gYyD6V+oBN2Ii5WQj0AddemjBZBgNVHSAEUjBQMEQGCSsG\r\nAQQBvUcNAjA3MDUGCCsGAQUFBwIBFilodHRwOi8vd3d3LnRlbGVzZWMuZGUvc2Vy\r\ndmVycGFzcy9jcHMuaHRtbDAIBgZngQwBAgIwggEhBgNVHR8EggEYMIIBFDBFoEOg\r\nQYY/aHR0cDovL2NybC5zZXJ2ZXJwYXNzLnRlbGVzZWMuZGUvcmwvVGVsZVNlY19T\r\nZXJ2ZXJQYXNzX0RFLTEuY3JsMIHKoIHHoIHEhoHBbGRhcDovL2xkYXAuc2VydmVy\r\ncGFzcy50ZWxlc2VjLmRlL2NuPVRlbGVTZWMlMjBTZXJ2ZXJQYXNzJTIwREUtMSxv\r\ndT1ULVN5c3RlbXMlMjBUcnVzdCUyMENlbnRlcixvPVQtU3lzdGVtcyUyMEludGVy\r\nbmF0aW9uYWwlMjBHbWJILGM9ZGU/Y2VydGlmaWNhdGVSZXZvY2F0aW9ubGlzdD9i\r\nYXNlP2NlcnRpZmljYXRlUmV2b2NhdGlvbmxpc3Q9KjCCATkGCCsGAQUFBwEBBIIB\r\nKzCCAScwMwYIKwYBBQUHMAGGJ2h0dHA6Ly9vY3NwLnNlcnZlcnBhc3MudGVsZXNl\r\nYy5kZS9vY3NwcjBMBggrBgEFBQcwAoZAaHR0cDovL2NybC5zZXJ2ZXJwYXNzLnRl\r\nbGVzZWMuZGUvY3J0L1RlbGVTZWNfU2VydmVyUGFzc19ERS0xLmNlcjCBoQYIKwYB\r\nBQUHMAKGgZRsZGFwOi8vbGRhcC5zZXJ2ZXJwYXNzLnRlbGVzZWMuZGUvY249VGVs\r\nZVNlYyUyMFNlcnZlclBhc3MlMjBERS0xLG91PVQtU3lzdGVtcyUyMFRydXN0JTIw\r\nQ2VudGVyLG89VC1TeXN0ZW1zJTIwSW50ZXJuYXRpb25hbCUyMEdtYkgsYz1kZT9j\r\nQUNlcnRpZmljYXRlMAwGA1UdEwEB/wQCMAAwJAYDVR0RBB0wG4IMaW1hcC5nbXgu\r\nbmV0ggtpbWFwLmdteC5kZTANBgkqhkiG9w0BAQUFAAOCAQEADl442s2ouynDNEzl\r\nBPzhCxjp47TmKzgzrCGh1AH3+UOkw54mYU65UaY9cJ51jgOu+pjNB4KxVfcqyG5O\r\nJcowS09ZtZ+khb1OkzXAAbEqNj0oyH/1K550d5Ir5VbXF0ZdVKVTzEFRyjvUt5NJ\r\n/b6Q2bbY/sPm8QiK9SPaYkO5/3J1KB0u7PkqqfOhZ9UJE6mIN66T2bpFmAwle3zL\r\n6+L/VwYSgUNs/w2l6xWlYP3pFFaT12TO43Q057I3vP7yCt3QE/VLDpdQOQ3H0oy2\r\nJQGPJLFby9MPYNkmEmwjh4e95TAY4ZkZMe08ix0J1Smy8DwjTz6SXqYC+EC/PQlh\r\nE0UHxA==\r\n-----END CERTIFICATE-----',
pinned: true,
ignoreTLS: false
},
checkOutboxInterval: 5000,
iconPath: '/img/icon-128-chrome.png',
verificationUrl: '/verify/',
verificationUuidLength: 36,
dbVersion: 5,
appVersion: appVersion,
outboxMailboxPath: 'OUTBOX',
outboxMailboxName: 'Outbox',
outboxMailboxType: 'Outbox',
connDocTimeout: 5000,
imapUpdateBatchSize: 25
};
smtp: {
host: 'mail.gmx.net',
port: 587,
secure: false,
ca: '-----BEGIN CERTIFICATE-----\r\nMIIHbDCCBlSgAwIBAgIJAJ74Ek9UaA0TMA0GCSqGSIb3DQEBBQUAMIHJMQswCQYD\r\nVQQGEwJERTElMCMGA1UEChMcVC1TeXN0ZW1zIEludGVybmF0aW9uYWwgR21iSDEf\r\nMB0GA1UECxMWVC1TeXN0ZW1zIFRydXN0IENlbnRlcjEMMAoGA1UECBMDTlJXMQ4w\r\nDAYDVQQREwU1NzI1MDEQMA4GA1UEBxMHTmV0cGhlbjEgMB4GA1UECRMXVW50ZXJl\r\nIEluZHVzdHJpZXN0ci4gMjAxIDAeBgNVBAMTF1RlbGVTZWMgU2VydmVyUGFzcyBE\r\nRS0xMB4XDTEzMTExMjEwMTY0N1oXDTE2MTExNzIzNTk1OVowgZ0xCzAJBgNVBAYT\r\nAkRFMR4wHAYDVQQKDBUxJjEgTWFpbCAmIE1lZGlhIEdtYkgxHTAbBgNVBAgTFFJo\r\naW5lbGFuZC1QYWxhdGluYXRlMRIwEAYDVQQHEwlNb250YWJhdXIxJDAiBgkqhkiG\r\n9w0BCQEWFXNlcnZlci1jZXJ0c0AxdW5kMS5kZTEVMBMGA1UEAxMMbWFpbC5nbXgu\r\nbmV0MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAsEUYppbJXUpQELIL\r\nETAFUwreDfSmbyi9gJPUqquogROHEoRV+bBFZ2YCiHgLU8AggcLmM74HAQMFt8F5\r\neS2OyuEar3E3tNW5XFiI1QblTtY3B7o1coKyq/i+tV3b1jGq4+1aJeeD3UpcraBm\r\nk2XuQgQ6WAeY+XCYyI/OzZ56ZjQ17LHMMI0ayu7SSV+VBpG9loC9E5A82iVtZsqo\r\nSaUZpZf/xICpUgNJ+RlalSsoE6FdCywE/gEEmkpAUh+Mv0WvHITk6aqtMFWex8tE\r\n8cwJGQbmJGt9x8BExbhMY6uQ+HsnsDCPCMVh5JGKx7/e+qyxtHYGChOeI16m2+MT\r\nz47jSQIDAQABo4IDfzCCA3swHwYDVR0jBBgwFoAUYk8TzjZnhM0Z/KBPGYvvFVQB\r\nMhwwDgYDVR0PAQH/BAQDAgWgMB0GA1UdJQQWMBQGCCsGAQUFBwMCBggrBgEFBQcD\r\nATAdBgNVHQ4EFgQUH8mLfxsd5FEh9M+3t3rP6qLiFTEwWQYDVR0gBFIwUDBEBgkr\r\nBgEEAb1HDQIwNzA1BggrBgEFBQcCARYpaHR0cDovL3d3dy50ZWxlc2VjLmRlL3Nl\r\ncnZlcnBhc3MvY3BzLmh0bWwwCAYGZ4EMAQICMIIBIQYDVR0fBIIBGDCCARQwRaBD\r\noEGGP2h0dHA6Ly9jcmwuc2VydmVycGFzcy50ZWxlc2VjLmRlL3JsL1RlbGVTZWNf\r\nU2VydmVyUGFzc19ERS0xLmNybDCByqCBx6CBxIaBwWxkYXA6Ly9sZGFwLnNlcnZl\r\ncnBhc3MudGVsZXNlYy5kZS9jbj1UZWxlU2VjJTIwU2VydmVyUGFzcyUyMERFLTEs\r\nb3U9VC1TeXN0ZW1zJTIwVHJ1c3QlMjBDZW50ZXIsbz1ULVN5c3RlbXMlMjBJbnRl\r\ncm5hdGlvbmFsJTIwR21iSCxjPWRlP2NlcnRpZmljYXRlUmV2b2NhdGlvbmxpc3Q/\r\nYmFzZT9jZXJ0aWZpY2F0ZVJldm9jYXRpb25saXN0PSowggE5BggrBgEFBQcBAQSC\r\nASswggEnMDMGCCsGAQUFBzABhidodHRwOi8vb2NzcC5zZXJ2ZXJwYXNzLnRlbGVz\r\nZWMuZGUvb2NzcHIwTAYIKwYBBQUHMAKGQGh0dHA6Ly9jcmwuc2VydmVycGFzcy50\r\nZWxlc2VjLmRlL2NydC9UZWxlU2VjX1NlcnZlclBhc3NfREUtMS5jZXIwgaEGCCsG\r\nAQUFBzAChoGUbGRhcDovL2xkYXAuc2VydmVycGFzcy50ZWxlc2VjLmRlL2NuPVRl\r\nbGVTZWMlMjBTZXJ2ZXJQYXNzJTIwREUtMSxvdT1ULVN5c3RlbXMlMjBUcnVzdCUy\r\nMENlbnRlcixvPVQtU3lzdGVtcyUyMEludGVybmF0aW9uYWwlMjBHbWJILGM9ZGU/\r\nY0FDZXJ0aWZpY2F0ZTAMBgNVHRMBAf8EAjAAMD8GA1UdEQQ4MDaCDG1haWwuZ214\r\nLm5ldIILbWFpbC5nbXguZGWCDHNtdHAuZ214Lm5ldIILc210cC5nbXguZGUwDQYJ\r\nKoZIhvcNAQEFBQADggEBAJ5SKsBXoOTO0ztsFh8RVd/iu6sijEMpKVF+/cs74TZN\r\nK1QWIz8Ay1f3SUPWXTvkmvayUXWGtxZfYoRIrx3feWBdUozJxe0wz1O3tDQOQgRX\r\nnkcwk7nibTUmW2rk5AIwV0jzOHtIbAGjSdc6my0543e7dVkhcuoCOC+g2NmD+pHF\r\ni1KpmqTLecDlFvprIZUJ23AT1uTnuXYg/tFHeDn3ga00Gce82xZQJelRirJs01SR\r\nDGEBaPXBjJVJNoAP/qEy3jQNRWo5TbSgwn3DTR81FJvltmf0c+zlt4fmcpjpr5ni\r\nFvK7L0rZQL5MOGHPpgRIukEBZCowYr3OYpZYBEaB94I=\r\n-----END CERTIFICATE-----',
pinned: true,
ignoreTLS: false
}
},
webde: {
imap: {
host: 'imap.web.de',
port: 993,
secure: true,
ca: '-----BEGIN CERTIFICATE-----\r\nMIIHUjCCBjqgAwIBAgIIHvqUOScyxYUwDQYJKoZIhvcNAQEFBQAwgckxCzAJBgNV\r\nBAYTAkRFMSUwIwYDVQQKExxULVN5c3RlbXMgSW50ZXJuYXRpb25hbCBHbWJIMR8w\r\nHQYDVQQLExZULVN5c3RlbXMgVHJ1c3QgQ2VudGVyMQwwCgYDVQQIEwNOUlcxDjAM\r\nBgNVBBETBTU3MjUwMRAwDgYDVQQHEwdOZXRwaGVuMSAwHgYDVQQJExdVbnRlcmUg\r\nSW5kdXN0cmllc3RyLiAyMDEgMB4GA1UEAxMXVGVsZVNlYyBTZXJ2ZXJQYXNzIERF\r\nLTEwHhcNMTMwODIxMDgzMTE2WhcNMTYwODI2MjM1OTU5WjCBrTELMAkGA1UEBhMC\r\nREUxHjAcBgNVBAoMFTEmMSBNYWlsICYgTWVkaWEgR21iSDEPMA0GA1UECxMGV0VC\r\nLkRFMR0wGwYDVQQIExRSaGluZWxhbmQtUGFsYXRpbmF0ZTESMBAGA1UEBxMJTW9u\r\ndGFiYXVyMSQwIgYJKoZIhvcNAQkBFhVzZXJ2ZXItY2VydHNAMXVuZDEuZGUxFDAS\r\nBgNVBAMTC2ltYXAud2ViLmRlMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKC\r\nAQEA5dvpcnYczFs9GANTYN5N3mP6ATFVm7P4nYhRIQMj/YssSjmbrOPIEbe12JNC\r\nRO9CI7Z4Dnk42BM5TiH+QKGpcfnitofOv4gKPjwCMcPDHfY152/+YDaiyU/md2Hg\r\n/WrZ/50KwC8Sw1tZkYDXWB0zeJkfPdS2r4ATNrpMR0fYcG08+elz5T2SWNg+c1xL\r\nKFdCh33wZLkijOfW0HA164QjXaLPBjxa+GyZFY19ywOQ85KdFVVLmGUrLz8n4ZLc\r\n7K6KUuzYttnUuVxctFauQ4DRHE/CfUvvNHHgn5d+A2XS7jNUgmUb0gjzy3OtlTP3\r\ngM4ostVufmnlS7qqFDR7A3v4lQIDAQABo4IDVjCCA1IwHwYDVR0jBBgwFoAUYk8T\r\nzjZnhM0Z/KBPGYvvFVQBMhwwDgYDVR0PAQH/BAQDAgWgMB0GA1UdJQQWMBQGCCsG\r\nAQUFBwMCBggrBgEFBQcDATAdBgNVHQ4EFgQUDH66P/Y4NlKGphPniIzmutEI9ocw\r\nWQYDVR0gBFIwUDBEBgkrBgEEAb1HDQIwNzA1BggrBgEFBQcCARYpaHR0cDovL3d3\r\ndy50ZWxlc2VjLmRlL3NlcnZlcnBhc3MvY3BzLmh0bWwwCAYGZ4EMAQICMIIBIQYD\r\nVR0fBIIBGDCCARQwRaBDoEGGP2h0dHA6Ly9jcmwuc2VydmVycGFzcy50ZWxlc2Vj\r\nLmRlL3JsL1RlbGVTZWNfU2VydmVyUGFzc19ERS0xLmNybDCByqCBx6CBxIaBwWxk\r\nYXA6Ly9sZGFwLnNlcnZlcnBhc3MudGVsZXNlYy5kZS9jbj1UZWxlU2VjJTIwU2Vy\r\ndmVyUGFzcyUyMERFLTEsb3U9VC1TeXN0ZW1zJTIwVHJ1c3QlMjBDZW50ZXIsbz1U\r\nLVN5c3RlbXMlMjBJbnRlcm5hdGlvbmFsJTIwR21iSCxjPWRlP2NlcnRpZmljYXRl\r\nUmV2b2NhdGlvbmxpc3Q/YmFzZT9jZXJ0aWZpY2F0ZVJldm9jYXRpb25saXN0PSow\r\nggE5BggrBgEFBQcBAQSCASswggEnMDMGCCsGAQUFBzABhidodHRwOi8vb2NzcC5z\r\nZXJ2ZXJwYXNzLnRlbGVzZWMuZGUvb2NzcHIwTAYIKwYBBQUHMAKGQGh0dHA6Ly9j\r\ncmwuc2VydmVycGFzcy50ZWxlc2VjLmRlL2NydC9UZWxlU2VjX1NlcnZlclBhc3Nf\r\nREUtMS5jZXIwgaEGCCsGAQUFBzAChoGUbGRhcDovL2xkYXAuc2VydmVycGFzcy50\r\nZWxlc2VjLmRlL2NuPVRlbGVTZWMlMjBTZXJ2ZXJQYXNzJTIwREUtMSxvdT1ULVN5\r\nc3RlbXMlMjBUcnVzdCUyMENlbnRlcixvPVQtU3lzdGVtcyUyMEludGVybmF0aW9u\r\nYWwlMjBHbWJILGM9ZGU/Y0FDZXJ0aWZpY2F0ZTAMBgNVHRMBAf8EAjAAMBYGA1Ud\r\nEQQPMA2CC2ltYXAud2ViLmRlMA0GCSqGSIb3DQEBBQUAA4IBAQAp7MQjjOWQ0M7N\r\n21GrPDfWSMR3eJnuMs37I9G2t9i99w7xKtmoVBPyYMORL1zRHn/DEguo4j5ua7CH\r\nrKLh9Sd1wDKqVWGFxvmP4f/mvEx5YVI68mg+M2VQf/h58IXTTZRbUcsv3HVeruI6\r\npgQNUsEQqRVJmrgT/iPd98RhhzBqef6Wfrt3Ns6N835egphxUVcVj/v/PqBCZKQb\r\nNl5QsoaHDqh3XC+Og2awOGvWHUzxUKEqkP5nsMs2YjfOtcQRtxdislQAfRktuUBA\r\nWMZAlvoVF/CIyFvcUH11KNur7HfK5PptvtdvLQaF2c+lN+LGP6D+nbTLqAo1v09o\r\nnZpHbQOt\r\n-----END CERTIFICATE-----',
pinned: true,
ignoreTLS: false
},
smtp: {
host: 'smtp.web.de',
port: 587,
secure: false,
ca: '-----BEGIN CERTIFICATE-----\r\nMIIHUzCCBjugAwIBAgIJAKaEwtMgQ2s5MA0GCSqGSIb3DQEBBQUAMIHJMQswCQYD\r\nVQQGEwJERTElMCMGA1UEChMcVC1TeXN0ZW1zIEludGVybmF0aW9uYWwgR21iSDEf\r\nMB0GA1UECxMWVC1TeXN0ZW1zIFRydXN0IENlbnRlcjEMMAoGA1UECBMDTlJXMQ4w\r\nDAYDVQQREwU1NzI1MDEQMA4GA1UEBxMHTmV0cGhlbjEgMB4GA1UECRMXVW50ZXJl\r\nIEluZHVzdHJpZXN0ci4gMjAxIDAeBgNVBAMTF1RlbGVTZWMgU2VydmVyUGFzcyBE\r\nRS0xMB4XDTEzMDgyMTA4MzU1MVoXDTE2MDgyNjIzNTk1OVowga0xCzAJBgNVBAYT\r\nAkRFMR4wHAYDVQQKDBUxJjEgTWFpbCAmIE1lZGlhIEdtYkgxDzANBgNVBAsTBldF\r\nQi5ERTEdMBsGA1UECBMUUmhpbmVsYW5kLVBhbGF0aW5hdGUxEjAQBgNVBAcTCU1v\r\nbnRhYmF1cjEkMCIGCSqGSIb3DQEJARYVc2VydmVyLWNlcnRzQDF1bmQxLmRlMRQw\r\nEgYDVQQDEwtzbXRwLndlYi5kZTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoC\r\nggEBAL1WfyFqmifaEr6h9yntJKs33fnEVu0pQqS5RV9rnQU0E/cJYJ9RJOoTjhpE\r\nkT1LW8DyTM5vUzpgtFHQM0BOO6jOeZXUYNUaopK3yO4l/cpiitigtk9ZnWfKzbU3\r\n9hlhf+YwUUvoqjUA8I6fSu+VNPNlOBvamo18oHljXAPltL9oiwAxvTLNktBMy4T9\r\njxU1DeGoPbJKGwV7zIBQ2qUHuLkMvy5/H39t79Tih+zfzgfY/xIUfdmPNY9dK8ZY\r\nAtyF/RiUS199pd9dV4Vwh6JLvtNlWchBnKVhragLCGbkjnvUXmS1BPrclLR23s1v\r\nMehgOD2WhXl8PoqrOPb6y+lgMlECAwEAAaOCA1YwggNSMB8GA1UdIwQYMBaAFGJP\r\nE842Z4TNGfygTxmL7xVUATIcMA4GA1UdDwEB/wQEAwIFoDAdBgNVHSUEFjAUBggr\r\nBgEFBQcDAgYIKwYBBQUHAwEwHQYDVR0OBBYEFAUuppozNCv2UzMV5p6CTpkEhsRS\r\nMFkGA1UdIARSMFAwRAYJKwYBBAG9Rw0CMDcwNQYIKwYBBQUHAgEWKWh0dHA6Ly93\r\nd3cudGVsZXNlYy5kZS9zZXJ2ZXJwYXNzL2Nwcy5odG1sMAgGBmeBDAECAjCCASEG\r\nA1UdHwSCARgwggEUMEWgQ6BBhj9odHRwOi8vY3JsLnNlcnZlcnBhc3MudGVsZXNl\r\nYy5kZS9ybC9UZWxlU2VjX1NlcnZlclBhc3NfREUtMS5jcmwwgcqggceggcSGgcFs\r\nZGFwOi8vbGRhcC5zZXJ2ZXJwYXNzLnRlbGVzZWMuZGUvY249VGVsZVNlYyUyMFNl\r\ncnZlclBhc3MlMjBERS0xLG91PVQtU3lzdGVtcyUyMFRydXN0JTIwQ2VudGVyLG89\r\nVC1TeXN0ZW1zJTIwSW50ZXJuYXRpb25hbCUyMEdtYkgsYz1kZT9jZXJ0aWZpY2F0\r\nZVJldm9jYXRpb25saXN0P2Jhc2U/Y2VydGlmaWNhdGVSZXZvY2F0aW9ubGlzdD0q\r\nMIIBOQYIKwYBBQUHAQEEggErMIIBJzAzBggrBgEFBQcwAYYnaHR0cDovL29jc3Au\r\nc2VydmVycGFzcy50ZWxlc2VjLmRlL29jc3ByMEwGCCsGAQUFBzAChkBodHRwOi8v\r\nY3JsLnNlcnZlcnBhc3MudGVsZXNlYy5kZS9jcnQvVGVsZVNlY19TZXJ2ZXJQYXNz\r\nX0RFLTEuY2VyMIGhBggrBgEFBQcwAoaBlGxkYXA6Ly9sZGFwLnNlcnZlcnBhc3Mu\r\ndGVsZXNlYy5kZS9jbj1UZWxlU2VjJTIwU2VydmVyUGFzcyUyMERFLTEsb3U9VC1T\r\neXN0ZW1zJTIwVHJ1c3QlMjBDZW50ZXIsbz1ULVN5c3RlbXMlMjBJbnRlcm5hdGlv\r\nbmFsJTIwR21iSCxjPWRlP2NBQ2VydGlmaWNhdGUwDAYDVR0TAQH/BAIwADAWBgNV\r\nHREEDzANggtzbXRwLndlYi5kZTANBgkqhkiG9w0BAQUFAAOCAQEAGFtJxVxWrURy\r\nFfR4UfmW+N1cZZx9sfC5jolV8LGje87DgbWdqu5TRL9FoQ1pwOTbM9mc3yWSoIbU\r\nx6E+rziJK+SFGMIy+Lt13P9M9Oc8JzVHoVEAgqlEeO6OYxrGE6SaSjZODJxPaEtU\r\nzEAKf0HVJQoGaU6fD+/8l3yhksAlsF/L85nP+KcZtoancOkJWE0GQMZp7pdLU0Ou\r\nFUQoAcqMIyHQfDqJ5iwvx/9C7jmy3Nvw9tXdPrdn2O7ywrnFeJuT2xiorZzg6ezn\r\nUlc/sMvd/LPX0f60pSQr9tkZgU4f8Jvx9EvPUCFTRXlXqkBIhgJhgCadZC+wKB1P\r\neQMS8rksXw==\r\n-----END CERTIFICATE-----',
pinned: true,
ignoreTLS: false
}
},
checkOutboxInterval: 5000,
iconPath: '/img/icon-128-chrome.png',
verificationUrl: '/verify/',
verificationUuidLength: 36,
dbVersion: 5,
appVersion: appVersion,
outboxMailboxPath: 'OUTBOX',
outboxMailboxName: 'Outbox',
outboxMailboxType: 'Outbox',
connDocTimeout: 5000,
imapUpdateBatchSize: 25
};
/**
* Strings are maintained here
*/
app.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',
signature: '\n\n\n--\nSent from Whiteout Mail - https://whiteout.io\n\nMy PGP key: ',
webSite: 'http://whiteout.io',
verificationSubject: '[whiteout] New public key uploaded',
sendBtnClear: 'Send',
sendBtnSecure: 'Send securely',
updatePublicKeyTitle: 'Public Key Updated',
updatePublicKeyMsgNewKey: '{0} updated his key and may not be able to read encrypted messages sent with his old key. Update the key?',
updatePublicKeyMsgRemovedKey: '{0} revoked his key and may no longer be able to read encrypted messages. Remove the key?',
updatePublicKeyPosBtn: 'Yes',
updatePublicKeyNegBtn: 'No',
outdatedCertificateTitle: 'Warning',
outdatedCertificateMessage: 'The SSL certificate for the mail server {0} changed, the connection was refused.',
updateCertificateTitle: 'Warning',
updateCertificateMessage: 'The SSL certificate for the mail server {0} changed. Do you want to proceed?',
updateCertificatePosBtn: 'Yes',
updateCertificateNegBtn: 'No',
certificateFaqLink: 'https://github.com/whiteout-io/mail-html5/wiki/FAQ#what-does-the-ssl-certificate-for-the-mail-server--changed-mean',
bugReportTitle: 'Report a bug',
bugReportSubject: '[Bug] I want to report a bug',
bugReportBody: 'Steps to reproduce\n1. \n2. \n3. \n\nWhat happens?\n\n\nWhat do you expect to happen instead?\n\n\n\n== PLEASE DONT PUT ANY KEYS HERE! ==\n\n\n## Log\n\nBelow is the log. It includes your interactions with your email provider in an anonymized way from the point where you started the app for the last time. Any information provided by you will be used for the porpose of locating and fixing the bug you reported. It will be deleted subsequently. However, you can edit this log and/or remove log data in the event that something would show up.\n\n',
supportAddress: 'mail.support@whiteout.io',
connDocOffline: 'It appears that you are offline. Please retry when you are online.',
connDocTlsWrongCert: 'A connection to {0} was rejected because the TLS certificate is invalid. Please have a look at the FAQ for information on how to fix this error.',
connDocHostUnreachable: 'We could not establish a connection to {0}. Please check the server settings!',
connDocHostTimeout: 'We could not establish a connection to {0} within {1} ms. Please check the server settings and encryption mode!',
connDocAuthRejected: 'Your credentials for {0} were rejected. Please check your username and password!',
connDocNoInbox: 'We could not detect an IMAP inbox folder on {0}. Please have a look at the FAQ for information on how to fix this error.',
connDocGenericError: 'There was an error connecting to {0}: {1}'
};
return app;
});
/**
* Strings are maintained here
*/
exports.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',
signature: '\n\n\n--\nSent from Whiteout Mail - https://whiteout.io\n\nMy PGP key: ',
webSite: 'http://whiteout.io',
verificationSubject: '[whiteout] New public key uploaded',
sendBtnClear: 'Send',
sendBtnSecure: 'Send securely',
updatePublicKeyTitle: 'Public Key Updated',
updatePublicKeyMsgNewKey: '{0} updated his key and may not be able to read encrypted messages sent with his old key. Update the key?',
updatePublicKeyMsgRemovedKey: '{0} revoked his key and may no longer be able to read encrypted messages. Remove the key?',
updatePublicKeyPosBtn: 'Yes',
updatePublicKeyNegBtn: 'No',
outdatedCertificateTitle: 'Warning',
outdatedCertificateMessage: 'The SSL certificate for the mail server {0} changed, the connection was refused.',
updateCertificateTitle: 'Warning',
updateCertificateMessage: 'The SSL certificate for the mail server {0} changed. Do you want to proceed?',
updateCertificatePosBtn: 'Yes',
updateCertificateNegBtn: 'No',
certificateFaqLink: 'https://github.com/whiteout-io/mail-html5/wiki/FAQ#what-does-the-ssl-certificate-for-the-mail-server--changed-mean',
bugReportTitle: 'Report a bug',
bugReportSubject: '[Bug] I want to report a bug',
bugReportBody: 'Steps to reproduce\n1. \n2. \n3. \n\nWhat happens?\n\n\nWhat do you expect to happen instead?\n\n\n\n== PLEASE DONT PUT ANY KEYS HERE! ==\n\n\n## Log\n\nBelow is the log. It includes your interactions with your email provider in an anonymized way from the point where you started the app for the last time. Any information provided by you will be used for the porpose of locating and fixing the bug you reported. It will be deleted subsequently. However, you can edit this log and/or remove log data in the event that something would show up.\n\n',
supportAddress: 'mail.support@whiteout.io',
connDocOffline: 'It appears that you are offline. Please retry when you are online.',
connDocTlsWrongCert: 'A connection to {0} was rejected because the TLS certificate is invalid. Please have a look at the FAQ for information on how to fix this error.',
connDocHostUnreachable: 'We could not establish a connection to {0}. Please check the server settings!',
connDocHostTimeout: 'We could not establish a connection to {0} within {1} ms. Please check the server settings and encryption mode!',
connDocAuthRejected: 'Your credentials for {0} were rejected. Please check your username and password!',
connDocNoInbox: 'We could not detect an IMAP inbox folder on {0}. Please have a look at the FAQ for information on how to fix this error.',
connDocGenericError: 'There was an error connecting to {0}: {1}'
};

View File

@ -1,271 +1,271 @@
/**
* The main application controller
*/
define(function(require) {
'use strict';
var axe = require('axe'),
Auth = require('js/bo/auth'),
PGP = require('js/crypto/pgp'),
PgpMailer = require('pgpmailer'),
OAuth = require('js/util/oauth'),
PgpBuilder = require('pgpbuilder'),
OutboxBO = require('js/bo/outbox'),
mailreader = require('mailreader'),
ImapClient = require('imap-client'),
Crypto = require('js/crypto/crypto'),
RestDAO = require('js/dao/rest-dao'),
appConfig = require('js/app-config'),
EmailDAO = require('js/dao/email-dao'),
AdminDao = require('js/dao/admin-dao'),
KeychainDAO = require('js/dao/keychain-dao'),
PublicKeyDAO = require('js/dao/publickey-dao'),
LawnchairDAO = require('js/dao/lawnchair-dao'),
PrivateKeyDAO = require('js/dao/privatekey-dao'),
InvitationDAO = require('js/dao/invitation-dao'),
DeviceStorageDAO = require('js/dao/devicestorage-dao'),
ConnectionDoctor = require('js/util/connection-doctor'),
UpdateHandler = require('js/util/update/update-handler'),
config = appConfig.config,
str = appConfig.string;
'use strict';
var self = {};
var axe = require('axe-logger'),
Auth = require('./bo/auth'),
PGP = require('./crypto/pgp'),
PgpMailer = require('pgpmailer'),
OAuth = require('./util/oauth'),
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;
/**
* Start the application.
*/
self.start = function(options, callback) {
if (self.started) {
return callback();
}
var ctrl = {};
self.started = true;
self.onError = options.onError;
/**
* Start the application.
*/
ctrl.start = function(options, callback) {
if (ctrl.started) {
return callback();
}
// 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();
}
ctrl.started = true;
ctrl.onError = options.onError;
function onDeviceReady() {
axe.debug('Starting app.');
// 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();
}
self.buildModules();
function onDeviceReady() {
axe.debug('Starting app.');
// Handle offline and online gracefully
window.addEventListener('online', self.onConnect.bind(self, self.onError));
window.addEventListener('offline', self.onDisconnect.bind(self));
ctrl.buildModules();
self._appConfigStore.init('app-config', callback);
}
};
// Handle offline and online gracefully
window.addEventListener('online', ctrl.onConnect.bind(ctrl, ctrl.onError));
window.addEventListener('offline', ctrl.onDisconnect.bind(ctrl));
/**
* Initialize the dependency tree.
*/
self.buildModules = function() {
var lawnchairDao, restDao, pubkeyDao, privkeyDao, crypto, emailDao, keychain, pgp, userStorage, pgpbuilder, oauth, appConfigStore, auth;
ctrl._appConfigStore.init('app-config', callback);
}
};
// start the mailreader's worker thread
mailreader.startWorker(config.workerPath + '/../lib/mailreader-parser-worker.js');
/**
* Initialize the dependency tree.
*/
ctrl.buildModules = function() {
var lawnchairDao, restDao, pubkeyDao, privkeyDao, crypto, emailDao, keychain, pgp, userStorage, pgpbuilder, oauth, appConfigStore, auth;
// 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'));
// start the mailreader's worker thread
mailreader.startWorker(config.workerPath + '/mailreader-parser-worker.min.js');
crypto = new Crypto();
self._pgp = pgp = new PGP();
self._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);
// 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'));
self.onError({
title: str.updatePublicKeyTitle,
message: message,
positiveBtnStr: str.updatePublicKeyPosBtn,
negativeBtnStr: str.updatePublicKeyNegBtn,
showNegativeBtn: true,
callback: callback
});
};
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);
self._appConfigStore = appConfigStore = new DeviceStorageDAO(new LawnchairDAO());
self._auth = auth = new Auth(appConfigStore, oauth, pgp);
self._userStorage = userStorage = new DeviceStorageDAO(lawnchairDao);
self._invitationDao = new InvitationDAO(restDao);
self._pgpbuilder = pgpbuilder = new PgpBuilder();
self._emailDao = emailDao = new EmailDAO(keychain, pgp, userStorage, pgpbuilder, mailreader);
self._outboxBo = new OutboxBO(emailDao, keychain, userStorage);
self._updateHandler = new UpdateHandler(appConfigStore, userStorage, auth);
self._adminDao = new AdminDao(new RestDAO(config.adminUrl));
self._doctor = new ConnectionDoctor();
emailDao.onError = self.onError;
};
/**
* Calls runtime hooks to check if an app update is available.
*/
self.checkForUpdate = function() {
self._updateHandler.checkForUpdate(self.onError);
};
/**
* Instanciate the mail email data access object and its dependencies. Login to imap on init.
*/
self.init = function(options, callback) {
// init user's local database
self._userStorage.init(options.emailAddress, function(err) {
if (err) {
callback(err);
return;
}
// Migrate the databases if necessary
self._updateHandler.update(onUpdate);
});
function onUpdate(err) {
if (err) {
callback({
errMsg: 'Update failed, please reinstall the app.',
err: err
});
return;
}
// account information for the email dao
var account = {
realname: options.realname,
emailAddress: options.emailAddress,
asymKeySize: config.asymKeySize
};
// init email dao
self._emailDao.init({
account: account
}, function(err, keypair) {
if (err) {
callback(err);
return;
}
callback(null, keypair);
});
}
};
/**
* Check if the user agent is online.
*/
self.isOnline = function() {
return navigator.onLine;
};
/**
* Event handler that is called when the user agent goes offline.
*/
self.onDisconnect = function() {
self._emailDao.onDisconnect();
};
/**
* Log the current user out by clear the app config store and deleting instances of imap-client and pgp-mailer.
*/
self.logout = function() {
var self = this;
// clear app config store
self._auth.logout(function(err) {
if (err) {
self.onError(err);
return;
}
// delete instance of imap-client and pgp-mailer
self._emailDao.onDisconnect(function(err) {
if (err) {
self.onError(err);
return;
}
// navigate to login
window.location.href = '/';
});
ctrl.onError({
title: str.updatePublicKeyTitle,
message: message,
positiveBtnStr: str.updatePublicKeyPosBtn,
negativeBtnStr: str.updatePublicKeyNegBtn,
showNegativeBtn: true,
callback: callback
});
};
/**
* 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.
*/
self.onConnect = function(callback) {
if (!self.isOnline() || !self._emailDao || !self._emailDao._account) {
// prevent connection infinite loop
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);
};
/**
* Instanciate the mail email data access object and its dependencies. Login to imap on init.
*/
ctrl.init = function(options, callback) {
// init user's local database
ctrl._userStorage.init(options.emailAddress, function(err) {
if (err) {
callback(err);
return;
}
self._auth.getCredentials(function(err, credentials) {
// Migrate the databases if necessary
ctrl._updateHandler.update(onUpdate);
});
function onUpdate(err) {
if (err) {
callback({
errMsg: 'Update failed, please reinstall the app.',
err: err
});
return;
}
// account information for the email dao
var account = {
realname: options.realname,
emailAddress: options.emailAddress,
asymKeySize: config.asymKeySize
};
// init email dao
ctrl._emailDao.init({
account: account
}, function(err, keypair) {
if (err) {
callback(err);
return;
}
initClients(credentials);
callback(null, keypair);
});
}
};
function initClients(credentials) {
// add the maximum update batch size for imap folders to the imap configuration
credentials.imap.maxUpdateSize = config.imapUpdateBatchSize;
/**
* Check if the user agent is online.
*/
ctrl.isOnline = function() {
return navigator.onLine;
};
var pgpMailer = new PgpMailer(credentials.smtp, self._pgpbuilder);
var imapClient = new ImapClient(credentials.imap);
imapClient.onError = onConnectionError;
pgpMailer.onError = onConnectionError;
/**
* Event handler that is called when the user agent goes offline.
*/
ctrl.onDisconnect = function() {
ctrl._emailDao.onDisconnect();
};
// certificate update handling
imapClient.onCert = self._auth.handleCertificateUpdate.bind(self._auth, 'imap', self.onConnect, self.onError);
pgpMailer.onCert = self._auth.handleCertificateUpdate.bind(self._auth, 'smtp', self.onConnect, self.onError);
// after-setup configuration depending on the provider:
// gmail does not require you to upload to the sent items folder
// after successful sending, whereas most other providers do
self._emailDao.ignoreUploadOnSent = !!(config[self._auth.provider] && config[self._auth.provider].ignoreUploadOnSent);
// connect to clients
self._emailDao.onConnect({
imapClient: imapClient,
pgpMailer: pgpMailer
}, callback);
/**
* 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;
}
function onConnectionError(error) {
axe.debug('Connection error. Attempting reconnect in ' + config.reconnectInterval + ' ms. Error: ' + (error.errMsg || error.message) + (error.stack ? ('\n' + error.stack) : ''));
// delete instance of imap-client and pgp-mailer
ctrl._emailDao.onDisconnect(function(err) {
if (err) {
ctrl.onError(err);
return;
}
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;
}
// navigate to login
window.location.href = '/';
});
});
};
axe.debug('Reconnect attempt complete.');
});
}, config.reconnectInterval);
/**
* 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;
}
};
return self;
});
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);
// after-setup configuration depending on the provider:
// gmail does not require you to upload to the sent items folder
// after successful sending, whereas most other providers do
ctrl._emailDao.ignoreUploadOnSent = !!(config[ctrl._auth.provider] && config[ctrl._auth.provider].ignoreUploadOnSent);
// connect to clients
ctrl._emailDao.onConnect({
imapClient: imapClient,
pgpMailer: pgpMailer
}, 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,148 +14,104 @@ if (typeof window.applicationCache !== 'undefined') {
};
}
// hey Angular, we're bootstrapping manually!
window.name = 'NG_DEFER_BOOTSTRAP!';
var DialogCtrl = require('./controller/dialog'),
PopoverCtrl = require('./controller/popover'),
AddAccountCtrl = require('./controller/add-account'),
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'),
errorUtil = require('./util/error'),
backButtonUtil = require('./util/backbutton-handler');
requirejs([
'angular',
'js/controller/dialog',
'js/controller/popover',
'js/controller/add-account',
'js/controller/account',
'js/controller/set-passphrase',
'js/controller/privatekey-upload',
'js/controller/contacts',
'js/controller/about',
'js/controller/login',
'js/controller/login-initial',
'js/controller/login-new-device',
'js/controller/login-existing',
'js/controller/login-privatekey-download',
'js/controller/login-set-credentials',
'js/controller/mail-list',
'js/controller/read',
'js/controller/write',
'js/controller/navigation',
'js/crypto/util',
'js/util/error',
'js/util/backbutton-handler',
'fastclick',
'angularRoute',
'angularAnimate',
'ngInfiniteScroll',
// init main angular module including dependencies
var app = angular.module('mail', [
'ngRoute',
'ngAnimate',
'navigation',
'mail-list',
'write',
'read',
'contacts',
'login-new-device',
'privatekey-upload',
'popover',
'infinite-scroll',
'ngTagsInput'
], function(
angular,
DialogCtrl,
PopoverCtrl,
AddAccountCtrl,
AccountCtrl,
SetPassphraseCtrl,
PrivateKeyUploadCtrl,
ContactsCtrl,
AboutCtrl,
LoginCtrl,
LoginInitialCtrl,
LoginNewDeviceCtrl,
LoginExistingCtrl,
LoginPrivateKeyDownloadCtrl,
LoginSetCredentialsCtrl,
MailListCtrl,
ReadCtrl,
WriteCtrl,
NavigationCtrl,
util,
errorUtil,
backButtonUtil,
FastClick
) {
// reset window.name
window.name = util.UUID();
]);
// init main angular module including dependencies
var app = angular.module('mail', [
'ngRoute',
'ngAnimate',
'navigation',
'mail-list',
'write',
'read',
'contacts',
'login-new-device',
'privatekey-upload',
'popover',
'infinite-scroll',
'ngTagsInput'
]);
// set router paths
app.config(function($routeProvider) {
$routeProvider.when('/add-account', {
templateUrl: 'tpl/add-account.html',
controller: AddAccountCtrl
});
$routeProvider.when('/login', {
templateUrl: 'tpl/login.html',
controller: LoginCtrl
});
$routeProvider.when('/login-set-credentials', {
templateUrl: 'tpl/login-set-credentials.html',
controller: LoginSetCredentialsCtrl
});
$routeProvider.when('/login-existing', {
templateUrl: 'tpl/login-existing.html',
controller: LoginExistingCtrl
});
$routeProvider.when('/login-initial', {
templateUrl: 'tpl/login-initial.html',
controller: LoginInitialCtrl
});
$routeProvider.when('/login-new-device', {
templateUrl: 'tpl/login-new-device.html',
controller: LoginNewDeviceCtrl
});
$routeProvider.when('/login-privatekey-download', {
templateUrl: 'tpl/login-privatekey-download.html',
controller: LoginPrivateKeyDownloadCtrl
});
$routeProvider.when('/desktop', {
templateUrl: 'tpl/desktop.html',
controller: NavigationCtrl
});
$routeProvider.otherwise({
redirectTo: '/login'
});
// set router paths
app.config(function($routeProvider) {
$routeProvider.when('/add-account', {
templateUrl: 'tpl/add-account.html',
controller: AddAccountCtrl
});
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);
// attach fastclick
FastClick.attach(document.body);
$routeProvider.when('/login', {
templateUrl: 'tpl/login.html',
controller: LoginCtrl
});
// inject controllers from ng-included view templates
app.controller('ReadCtrl', ReadCtrl);
app.controller('WriteCtrl', WriteCtrl);
app.controller('MailListCtrl', MailListCtrl);
app.controller('AccountCtrl', AccountCtrl);
app.controller('SetPassphraseCtrl', SetPassphraseCtrl);
app.controller('PrivateKeyUploadCtrl', PrivateKeyUploadCtrl);
app.controller('ContactsCtrl', ContactsCtrl);
app.controller('AboutCtrl', AboutCtrl);
app.controller('DialogCtrl', DialogCtrl);
app.controller('PopoverCtrl', PopoverCtrl);
// manually bootstrap angular due to require.js
angular.element().ready(function() {
angular.bootstrap(document, ['mail']);
$routeProvider.when('/login-set-credentials', {
templateUrl: 'tpl/login-set-credentials.html',
controller: LoginSetCredentialsCtrl
});
});
$routeProvider.when('/login-existing', {
templateUrl: 'tpl/login-existing.html',
controller: LoginExistingCtrl
});
$routeProvider.when('/login-initial', {
templateUrl: 'tpl/login-initial.html',
controller: LoginInitialCtrl
});
$routeProvider.when('/login-new-device', {
templateUrl: 'tpl/login-new-device.html',
controller: LoginNewDeviceCtrl
});
$routeProvider.when('/login-privatekey-download', {
templateUrl: 'tpl/login-privatekey-download.html',
controller: LoginPrivateKeyDownloadCtrl
});
$routeProvider.when('/desktop', {
templateUrl: 'tpl/desktop.html',
controller: NavigationCtrl
});
$routeProvider.otherwise({
redirectTo: '/login'
});
});
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);
// attach fastclick
FastClick.attach(document.body);
});
// inject controllers from ng-included view templates
app.controller('ReadCtrl', ReadCtrl);
app.controller('WriteCtrl', WriteCtrl);
app.controller('MailListCtrl', MailListCtrl);
app.controller('AccountCtrl', AccountCtrl);
app.controller('SetPassphraseCtrl', SetPassphraseCtrl);
app.controller('PrivateKeyUploadCtrl', PrivateKeyUploadCtrl);
app.controller('ContactsCtrl', ContactsCtrl);
app.controller('AboutCtrl', AboutCtrl);
app.controller('DialogCtrl', DialogCtrl);
app.controller('PopoverCtrl', PopoverCtrl);

View File

@ -1,355 +1,210 @@
define(function(require) {
'use strict';
'use strict';
var axe = require('axe'),
str = require('js/app-config').string;
var axe = require('axe-logger'),
str = require('../app-config').string;
var EMAIL_ADDR_DB_KEY = 'emailaddress';
var USERNAME_DB_KEY = 'username';
var REALNAME_DB_KEY = 'realname';
var PASSWD_DB_KEY = 'password';
var PROVIDER_DB_KEY = 'provider';
var IMAP_DB_KEY = 'imap';
var SMTP_DB_KEY = 'smtp';
var EMAIL_ADDR_DB_KEY = 'emailaddress';
var USERNAME_DB_KEY = 'username';
var REALNAME_DB_KEY = 'realname';
var PASSWD_DB_KEY = 'password';
var PROVIDER_DB_KEY = 'provider';
var IMAP_DB_KEY = 'imap';
var SMTP_DB_KEY = 'smtp';
/**
* The Auth BO handles the rough edges and gaps between user/password authentication
* and OAuth via Chrome Identity API.
* Typical usage:
* var auth = new Auth(...);
* auth.setCredentials(...); // during the account setup
* auth.getEmailAddress(...); // called from the login controller to determine if there is already a user present on the device
* auth.getCredentials(...); // called to gather all the information to connect to IMAP/SMTP, e.g. pinned intermediate certificates,
* username, password / oauth token, IMAP/SMTP server host names, ...
*/
var Auth = function(appConfigStore, oauth, pgp) {
this._appConfigStore = appConfigStore;
this._oauth = oauth;
this._pgp = pgp;
};
/**
* The Auth BO handles the rough edges and gaps between user/password authentication
* and OAuth via Chrome Identity API.
* Typical usage:
* var auth = new Auth(...);
* auth.setCredentials(...); // during the account setup
* auth.getEmailAddress(...); // called from the login controller to determine if there is already a user present on the device
* auth.getCredentials(...); // called to gather all the information to connect to IMAP/SMTP, e.g. pinned intermediate certificates,
* username, password / oauth token, IMAP/SMTP server host names, ...
*/
var Auth = function(appConfigStore, oauth, pgp) {
this._appConfigStore = appConfigStore;
this._oauth = oauth;
this._pgp = pgp;
};
/**
* Retrieves credentials and IMAP/SMTP settings:
* 1) Fetches the credentials from disk, then...
* 2 a) ... in an oauth setting, retrieves a fresh oauth token from the Chrome Identity API.
* 2 b) ... in a user/passwd setting, does not need to do additional work.
* 3) Loads the intermediate certs from the configuration.
*
* @param {Function} callback(err, credentials)
*/
Auth.prototype.getCredentials = function(callback) {
var self = this;
if (!self.provider || !self.emailAddress) {
// we're not yet initialized, so let's load our stuff from disk
self._loadCredentials(function(err) {
if (err) {
return callback(err);
}
chooseLogin();
});
return;
}
chooseLogin();
function chooseLogin() {
if (self.provider === 'gmail' && !self.password) {
// oauth login for gmail
self.getOAuthToken(function(err) {
if (err) {
return callback(err);
}
done();
});
return;
}
if (self.passwordNeedsDecryption) {
// decrypt password
self._pgp.decrypt(self.password, undefined, function(err, cleartext) {
if (err) {
return callback(err);
}
self.passwordNeedsDecryption = false;
self.password = cleartext;
done();
});
return;
}
done();
}
function done() {
var credentials = {
imap: {
secure: self.imap.secure,
port: self.imap.port,
host: self.imap.host,
ca: self.imap.ca,
pinned: self.imap.pinned,
auth: {
user: self.username,
xoauth2: self.oauthToken, // password or oauthToken is undefined
pass: self.password
}
},
smtp: {
secure: self.smtp.secure,
port: self.smtp.port,
host: self.smtp.host,
ca: self.smtp.ca,
pinned: self.smtp.pinned,
auth: {
user: self.username,
xoauth2: self.oauthToken,
pass: self.password // password or oauthToken is undefined
}
}
};
callback(null, credentials);
}
};
/**
* Set the credentials
*
* @param {String} options.provider The service provider, e.g. 'gmail', 'yahoo', 'tonline'. Matches the entry in the app-config.
* @param {String} options.emailAddress The email address
* @param {String} options.username The user name
* @param {String} options.realname The user's real name
* @param {String} options.password The password, only in user/passwd setting
* @param {String} options.smtp The smtp settings (host, port, secure)
* @param {String} options.imap The imap settings (host, port, secure)
*/
Auth.prototype.setCredentials = function(options) {
this.credentialsDirty = true;
this.provider = options.provider;
this.emailAddress = options.emailAddress;
this.username = options.username;
this.realname = options.realname ? options.realname : '';
this.password = options.password;
this.smtp = options.smtp; // host, port, secure, ca, pinned
this.imap = options.imap; // host, port, secure, ca, pinned
};
Auth.prototype.storeCredentials = function(callback) {
var self = this;
if (!self.credentialsDirty) {
return callback();
}
// persist the provider
self._appConfigStore.storeList([self.smtp], SMTP_DB_KEY, function(err) {
if (err) {
return callback(err);
}
self._appConfigStore.storeList([self.imap], IMAP_DB_KEY, function(err) {
if (err) {
return callback(err);
}
self._appConfigStore.storeList([self.provider], PROVIDER_DB_KEY, function(err) {
if (err) {
return callback(err);
}
self._appConfigStore.storeList([self.emailAddress], EMAIL_ADDR_DB_KEY, function(err) {
if (err) {
return callback(err);
}
self._appConfigStore.storeList([self.username], USERNAME_DB_KEY, function(err) {
if (err) {
return callback(err);
}
self._appConfigStore.storeList([self.realname], REALNAME_DB_KEY, function(err) {
if (err) {
return callback(err);
}
if (!self.password) {
self.credentialsDirty = false;
return callback();
}
if (self.passwordNeedsDecryption) {
// password is not decrypted yet, so no need to re-encrypt it before storing...
self._appConfigStore.storeList([self.password], PASSWD_DB_KEY, function(err) {
if (err) {
return callback(err);
}
self.credentialsDirty = false;
callback();
});
return;
}
self._pgp.encrypt(self.password, undefined, function(err, ciphertext) {
if (err) {
return callback(err);
}
self._appConfigStore.storeList([ciphertext], PASSWD_DB_KEY, function(err) {
if (err) {
return callback(err);
}
self.credentialsDirty = false;
callback();
});
});
});
});
});
});
});
});
};
/**
* Returns the email address. Loads it from disk, if necessary
*/
Auth.prototype.getEmailAddress = function(callback) {
var self = this;
if (self.emailAddress) {
return callback(null, {
emailAddress: self.emailAddress,
realname: self.realname
});
}
/**
* Retrieves credentials and IMAP/SMTP settings:
* 1) Fetches the credentials from disk, then...
* 2 a) ... in an oauth setting, retrieves a fresh oauth token from the Chrome Identity API.
* 2 b) ... in a user/passwd setting, does not need to do additional work.
* 3) Loads the intermediate certs from the configuration.
*
* @param {Function} callback(err, credentials)
*/
Auth.prototype.getCredentials = function(callback) {
var self = this;
if (!self.provider || !self.emailAddress) {
// we're not yet initialized, so let's load our stuff from disk
self._loadCredentials(function(err) {
if (err) {
return callback(err);
}
callback(null, {
emailAddress: self.emailAddress,
realname: self.realname
});
chooseLogin();
});
};
return;
}
/**
* READ FIRST b/c usage of the oauth api is weird.
* the chrome identity api will let you query an oauth token for an email account without knowing
* the corresponding email address. also, android has multiple accounts whereas desktop chrome only
* has one user logged in.
* 1) try to read the email address from the configuration (see above)
* 2) fetch the oauth token. if we already HAVE an email address at this point, we can spare
* popping up the account picker on android! if not, the account picker will pop up. this
* is android only, since the desktop chrome will query the user that is logged into chrome
* 3) fetch the email address for the oauth token from the chrome identity api
*/
Auth.prototype.getOAuthToken = function(callback) {
var self = this;
chooseLogin();
if (self.oauthToken) {
// removed cached token and get a new one
self._oauth.refreshToken({
emailAddress: self.emailAddress,
oldToken: self.oauthToken
}, onToken);
} else {
// get a fresh oauth token
self._oauth.getOAuthToken(self.emailAddress, onToken);
}
function onToken(err, oauthToken) {
if (err) {
return callback(err);
}
// shortcut if the email address is already known
if (self.emailAddress) {
self.oauthToken = oauthToken;
return callback();
}
// query the email address
self._oauth.queryEmailAddress(oauthToken, function(err, emailAddress) {
function chooseLogin() {
if (self.provider === 'gmail' && !self.password) {
// oauth login for gmail
self.getOAuthToken(function(err) {
if (err) {
return callback(err);
}
self.oauthToken = oauthToken;
self.emailAddress = emailAddress;
callback();
done();
});
}
};
/**
* Loads email address, password, provider, ... from disk and sets them on `this`
*/
Auth.prototype._loadCredentials = function(callback) {
var self = this;
if (self.initialized) {
callback();
return;
}
loadFromDB(SMTP_DB_KEY, function(err, smtp) {
if (err) {
return callback(err);
}
loadFromDB(IMAP_DB_KEY, function(err, imap) {
if (self.passwordNeedsDecryption) {
// decrypt password
self._pgp.decrypt(self.password, undefined, function(err, cleartext) {
if (err) {
return callback(err);
}
self.passwordNeedsDecryption = false;
self.password = cleartext;
loadFromDB(USERNAME_DB_KEY, function(err, username) {
done();
});
return;
}
done();
}
function done() {
var credentials = {
imap: {
secure: self.imap.secure,
port: self.imap.port,
host: self.imap.host,
ca: self.imap.ca,
pinned: self.imap.pinned,
auth: {
user: self.username,
xoauth2: self.oauthToken, // password or oauthToken is undefined
pass: self.password
}
},
smtp: {
secure: self.smtp.secure,
port: self.smtp.port,
host: self.smtp.host,
ca: self.smtp.ca,
pinned: self.smtp.pinned,
auth: {
user: self.username,
xoauth2: self.oauthToken,
pass: self.password // password or oauthToken is undefined
}
}
};
callback(null, credentials);
}
};
/**
* Set the credentials
*
* @param {String} options.provider The service provider, e.g. 'gmail', 'yahoo', 'tonline'. Matches the entry in the app-config.
* @param {String} options.emailAddress The email address
* @param {String} options.username The user name
* @param {String} options.realname The user's real name
* @param {String} options.password The password, only in user/passwd setting
* @param {String} options.smtp The smtp settings (host, port, secure)
* @param {String} options.imap The imap settings (host, port, secure)
*/
Auth.prototype.setCredentials = function(options) {
this.credentialsDirty = true;
this.provider = options.provider;
this.emailAddress = options.emailAddress;
this.username = options.username;
this.realname = options.realname ? options.realname : '';
this.password = options.password;
this.smtp = options.smtp; // host, port, secure, ca, pinned
this.imap = options.imap; // host, port, secure, ca, pinned
};
Auth.prototype.storeCredentials = function(callback) {
var self = this;
if (!self.credentialsDirty) {
return callback();
}
// persist the provider
self._appConfigStore.storeList([self.smtp], SMTP_DB_KEY, function(err) {
if (err) {
return callback(err);
}
self._appConfigStore.storeList([self.imap], IMAP_DB_KEY, function(err) {
if (err) {
return callback(err);
}
self._appConfigStore.storeList([self.provider], PROVIDER_DB_KEY, function(err) {
if (err) {
return callback(err);
}
self._appConfigStore.storeList([self.emailAddress], EMAIL_ADDR_DB_KEY, function(err) {
if (err) {
return callback(err);
}
loadFromDB(REALNAME_DB_KEY, function(err, realname) {
self._appConfigStore.storeList([self.username], USERNAME_DB_KEY, function(err) {
if (err) {
return callback(err);
}
loadFromDB(EMAIL_ADDR_DB_KEY, function(err, emailAddress) {
self._appConfigStore.storeList([self.realname], REALNAME_DB_KEY, function(err) {
if (err) {
return callback(err);
}
loadFromDB(PASSWD_DB_KEY, function(err, password) {
if (err) {
return callback(err);
}
if (!self.password) {
self.credentialsDirty = false;
return callback();
}
loadFromDB(PROVIDER_DB_KEY, function(err, provider) {
if (self.passwordNeedsDecryption) {
// password is not decrypted yet, so no need to re-encrypt it before storing...
self._appConfigStore.storeList([self.password], PASSWD_DB_KEY, function(err) {
if (err) {
return callback(err);
}
self.emailAddress = emailAddress;
self.password = password;
self.passwordNeedsDecryption = !!password;
self.provider = provider;
self.username = username;
self.realname = realname;
self.smtp = smtp;
self.imap = imap;
self.initialized = true;
self.credentialsDirty = false;
callback();
});
return;
}
self._pgp.encrypt(self.password, undefined, function(err, ciphertext) {
if (err) {
return callback(err);
}
self._appConfigStore.storeList([ciphertext], PASSWD_DB_KEY, function(err) {
if (err) {
return callback(err);
}
self.credentialsDirty = false;
callback();
});
});
@ -358,96 +213,239 @@ define(function(require) {
});
});
});
});
};
function loadFromDB(key, callback) {
self._appConfigStore.listItems(key, 0, null, function(err, cachedItems) {
callback(err, (!err && cachedItems && cachedItems[0]));
});
}
};
/**
* Returns the email address. Loads it from disk, if necessary
*/
Auth.prototype.getEmailAddress = function(callback) {
var self = this;
/**
* Handles certificate updates and errors by notifying the user.
* @param {String} component Either imap or smtp
* @param {Function} callback The error handler
* @param {[type]} pemEncodedCert The PEM encoded SSL certificate
*/
Auth.prototype.handleCertificateUpdate = function(component, onConnect, callback, pemEncodedCert) {
var self = this;
if (self.emailAddress) {
return callback(null, {
emailAddress: self.emailAddress,
realname: self.realname
});
}
axe.debug('new ssl certificate received: ' + pemEncodedCert);
if (!self[component].ca) {
// no previous ssl cert, trust on first use
self[component].ca = pemEncodedCert;
self.credentialsDirty = true;
self.storeCredentials(callback);
return;
self._loadCredentials(function(err) {
if (err) {
return callback(err);
}
if (self[component].ca === pemEncodedCert) {
// ignore multiple successive tls handshakes, e.g. for gmail
return;
callback(null, {
emailAddress: self.emailAddress,
realname: self.realname
});
});
};
/**
* READ FIRST b/c usage of the oauth api is weird.
* the chrome identity api will let you query an oauth token for an email account without knowing
* the corresponding email address. also, android has multiple accounts whereas desktop chrome only
* has one user logged in.
* 1) try to read the email address from the configuration (see above)
* 2) fetch the oauth token. if we already HAVE an email address at this point, we can spare
* popping up the account picker on android! if not, the account picker will pop up. this
* is android only, since the desktop chrome will query the user that is logged into chrome
* 3) fetch the email address for the oauth token from the chrome identity api
*/
Auth.prototype.getOAuthToken = function(callback) {
var self = this;
if (self.oauthToken) {
// removed cached token and get a new one
self._oauth.refreshToken({
emailAddress: self.emailAddress,
oldToken: self.oauthToken
}, onToken);
} else {
// get a fresh oauth token
self._oauth.getOAuthToken(self.emailAddress, onToken);
}
function onToken(err, oauthToken) {
if (err) {
return callback(err);
}
if (self[component].ca && self[component].pinned) {
// do not update the pinned certificates!
callback({
title: str.outdatedCertificateTitle,
message: str.outdatedCertificateMessage.replace('{0}', self[component].host),
faqLink: str.certificateFaqLink,
});
return;
// shortcut if the email address is already known
if (self.emailAddress) {
self.oauthToken = oauthToken;
return callback();
}
// previous ssl cert known, does not match: query user and certificate
callback({
title: str.updateCertificateTitle,
message: str.updateCertificateMessage.replace('{0}', self[component].host),
positiveBtnStr: str.updateCertificatePosBtn,
negativeBtnStr: str.updateCertificateNegBtn,
showNegativeBtn: true,
faqLink: str.certificateFaqLink,
callback: function(granted) {
if (!granted) {
return;
// query the email address
self._oauth.queryEmailAddress(oauthToken, function(err, emailAddress) {
if (err) {
return callback(err);
}
self.oauthToken = oauthToken;
self.emailAddress = emailAddress;
callback();
});
}
};
/**
* Loads email address, password, provider, ... from disk and sets them on `this`
*/
Auth.prototype._loadCredentials = function(callback) {
var self = this;
if (self.initialized) {
callback();
}
loadFromDB(SMTP_DB_KEY, function(err, smtp) {
if (err) {
return callback(err);
}
loadFromDB(IMAP_DB_KEY, function(err, imap) {
if (err) {
return callback(err);
}
loadFromDB(USERNAME_DB_KEY, function(err, username) {
if (err) {
return callback(err);
}
self[component].ca = pemEncodedCert;
self.storeCredentials(function(err) {
loadFromDB(REALNAME_DB_KEY, function(err, realname) {
if (err) {
callback(err);
return;
return callback(err);
}
onConnect(callback);
loadFromDB(EMAIL_ADDR_DB_KEY, function(err, emailAddress) {
if (err) {
return callback(err);
}
loadFromDB(PASSWD_DB_KEY, function(err, password) {
if (err) {
return callback(err);
}
loadFromDB(PROVIDER_DB_KEY, function(err, provider) {
if (err) {
return callback(err);
}
self.emailAddress = emailAddress;
self.password = password;
self.passwordNeedsDecryption = !!password;
self.provider = provider;
self.username = username;
self.realname = realname;
self.smtp = smtp;
self.imap = imap;
self.initialized = true;
callback();
});
});
});
});
}
});
});
};
});
/**
* Logout of the app by clearing the app config store and in memory credentials
*/
Auth.prototype.logout = function(callback) {
var self = this;
function loadFromDB(key, callback) {
self._appConfigStore.listItems(key, 0, null, function(err, cachedItems) {
callback(err, (!err && cachedItems && cachedItems[0]));
});
}
};
// clear app config db
self._appConfigStore.clear(function(err) {
if (err) {
callback(err);
/**
* Handles certificate updates and errors by notifying the user.
* @param {String} component Either imap or smtp
* @param {Function} callback The error handler
* @param {[type]} pemEncodedCert The PEM encoded SSL certificate
*/
Auth.prototype.handleCertificateUpdate = function(component, onConnect, callback, pemEncodedCert) {
var self = this;
axe.debug('new ssl certificate received: ' + pemEncodedCert);
if (!self[component].ca) {
// no previous ssl cert, trust on first use
self[component].ca = pemEncodedCert;
self.credentialsDirty = true;
self.storeCredentials(callback);
return;
}
if (self[component].ca === pemEncodedCert) {
// ignore multiple successive tls handshakes, e.g. for gmail
return;
}
if (self[component].ca && self[component].pinned) {
// do not update the pinned certificates!
callback({
title: str.outdatedCertificateTitle,
message: str.outdatedCertificateMessage.replace('{0}', self[component].host),
faqLink: str.certificateFaqLink,
});
return;
}
// previous ssl cert known, does not match: query user and certificate
callback({
title: str.updateCertificateTitle,
message: str.updateCertificateMessage.replace('{0}', self[component].host),
positiveBtnStr: str.updateCertificatePosBtn,
negativeBtnStr: str.updateCertificateNegBtn,
showNegativeBtn: true,
faqLink: str.certificateFaqLink,
callback: function(granted) {
if (!granted) {
return;
}
// clear in memory cache
self.setCredentials({});
self.initialized = undefined;
self.credentialsDirty = undefined;
self.passwordNeedsDecryption = undefined;
self[component].ca = pemEncodedCert;
self.storeCredentials(function(err) {
if (err) {
callback(err);
return;
}
callback();
});
};
onConnect(callback);
});
}
});
};
return Auth;
});
/**
* Logout of the app by clearing the app config store and in memory credentials
*/
Auth.prototype.logout = function(callback) {
var self = this;
// clear app config db
self._appConfigStore.clear(function(err) {
if (err) {
callback(err);
return;
}
// clear in memory cache
self.setCredentials({});
self.initialized = undefined;
self.credentialsDirty = undefined;
self.passwordNeedsDecryption = undefined;
callback();
});
};
module.exports = Auth;

View File

@ -1,232 +1,229 @@
define(function(require) {
'use strict';
'use strict';
var _ = require('underscore'),
util = require('js/crypto/util'),
config = require('js/app-config').config,
outboxDb = 'email_OUTBOX';
var util = require('crypto-lib').util,
config = require('../app-config').config,
outboxDb = 'email_OUTBOX';
/**
* High level business object that orchestrates the local outbox.
* 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) {
/** @private */
this._emailDao = emailDao;
/** @private */
this._keychain = keychain;
/** @private */
this._devicestorage = devicestorage;
/**
* High level business object that orchestrates the local outbox.
* 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) {
/** @private */
this._emailDao = emailDao;
* Semaphore-esque flag to avoid 'concurrent' calls to _processOutbox when the timeout fires, but a call is still in process.
* @private */
this._outboxBusy = false;
};
/** @private */
this._keychain = keychain;
/**
* 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) {
// remember global callback
this._onUpdate = callback;
// start periodic checking of outbox
this._intervalId = setInterval(this._processOutbox.bind(this, this._onUpdate), config.checkOutboxInterval);
};
/** @private */
this._devicestorage = devicestorage;
/**
* Outbox stops the periodic checking of the local device storage for pending mails.
*/
OutboxBO.prototype.stopChecking = function() {
if (!this._intervalId) {
return;
}
/**
* Semaphore-esque flag to avoid 'concurrent' calls to _processOutbox when the timeout fires, but a call is still in process.
* @private */
this._outboxBusy = false;
};
clearInterval(this._intervalId);
delete this._intervalId;
};
/**
* 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) {
// remember global callback
this._onUpdate = callback;
// start periodic checking of outbox
this._intervalId = setInterval(this._processOutbox.bind(this, this._onUpdate), config.checkOutboxInterval);
};
/**
* Put a email dto in the outbox for sending when ready
* @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) {
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
/**
* Outbox stops the periodic checking of the local device storage for pending mails.
*/
OutboxBO.prototype.stopChecking = function() {
if (!this._intervalId) {
return;
}
mail.publicKeysArmored = []; // gather the public keys
mail.uid = mail.id = util.UUID(); // the mail needs a random id & uid for storage in the database
clearInterval(this._intervalId);
delete this._intervalId;
};
// do not encrypt mails with a bcc recipient, due to a possible privacy leak
if (mail.bcc.length > 0) {
storeAndForward(mail);
return;
}
/**
* Put a email dto in the outbox for sending when ready
* @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) {
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
checkRecipients(allReaders);
mail.publicKeysArmored = []; // gather the public keys
mail.uid = mail.id = util.UUID(); // the mail needs a random id & uid for storage in the database
// check if there are unregistered recipients
function checkRecipients(recipients) {
var after = _.after(recipients.length, function() {
checkEncrypt();
});
// do not encrypt mails with a bcc recipient, due to a possible privacy leak
if (mail.bcc.length > 0) {
// find out if there are unregistered users
recipients.forEach(function(recipient) {
self._keychain.getReceiverPublicKey(recipient.address, function(err, key) {
if (err) {
callback(err);
return;
}
// if a public key is available, add the recipient's key to the armored public keys,
// otherwise remember the recipient as unregistered for later sending
if (key) {
mail.publicKeysArmored.push(key.publicKey);
}
after();
});
});
}
function checkEncrypt() {
// only encrypt if all recipients have public keys
if (mail.publicKeysArmored.length < allReaders.length) {
storeAndForward(mail);
return;
}
checkRecipients(allReaders);
// check if there are unregistered recipients
function checkRecipients(recipients) {
var after = _.after(recipients.length, function() {
checkEncrypt();
});
// find out if there are unregistered users
recipients.forEach(function(recipient) {
self._keychain.getReceiverPublicKey(recipient.address, function(err, key) {
if (err) {
callback(err);
return;
}
// if a public key is available, add the recipient's key to the armored public keys,
// otherwise remember the recipient as unregistered for later sending
if (key) {
mail.publicKeysArmored.push(key.publicKey);
}
after();
});
});
}
function checkEncrypt() {
// only encrypt if all recipients have public keys
if (mail.publicKeysArmored.length < allReaders.length) {
storeAndForward(mail);
// encrypts the body and attachments and persists the mail object
self._emailDao.encrypt({
mail: mail,
publicKeysArmored: mail.publicKeysArmored
}, function(err) {
if (err) {
callback(err);
return;
}
// encrypts the body and attachments and persists the mail object
self._emailDao.encrypt({
mail: mail,
publicKeysArmored: mail.publicKeysArmored
}, function(err) {
if (err) {
callback(err);
return;
}
storeAndForward(mail);
});
}
storeAndForward(mail);
});
}
function storeAndForward(mail) {
// store in outbox
self._devicestorage.storeList([mail], outboxDb, function(err) {
if (err) {
callback(err);
return;
}
function storeAndForward(mail) {
// store in outbox
self._devicestorage.storeList([mail], outboxDb, function(err) {
if (err) {
callback(err);
return;
}
callback();
// don't wait for next round
self._processOutbox(self._onUpdate);
});
}
};
callback();
// don't wait for next round
self._processOutbox(self._onUpdate);
});
}
};
/**
* 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) {
var self = this,
unsentMails = 0;
/**
* 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) {
var self = this,
unsentMails = 0;
// also, if a _processOutbox call is still in progress, ignore it.
if (self._outboxBusy) {
return;
}
// also, if a _processOutbox call is still in progress, ignore it.
if (self._outboxBusy) {
self._outboxBusy = true;
// get pending mails from the outbox
self._devicestorage.listItems(outboxDb, 0, null, function(err, pendingMails) {
// error, we're done here
if (err) {
self._outboxBusy = false;
callback(err);
return;
}
self._outboxBusy = true;
// if we're not online, don't even bother sending mails.
if (!self._emailDao._account.online || _.isEmpty(pendingMails)) {
self._outboxBusy = false;
callback(null, pendingMails.length);
return;
}
// get pending mails from the outbox
self._devicestorage.listItems(outboxDb, 0, null, function(err, pendingMails) {
// error, we're done here
// we're done after all the mails have been handled
// update the outbox count...
var after = _.after(pendingMails.length, function() {
self._outboxBusy = false;
callback(null, unsentMails);
});
// send pending mails if possible
pendingMails.forEach(function(mail) {
send(mail, after);
});
});
// send the message
function send(mail, done) {
// check is email is to be sent encrypted or as plaintex
if (mail.encrypted === true) {
// email was already encrypted before persisting in outbox, tell pgpmailer to send encrypted and not encrypt again
self._emailDao.sendEncrypted({
email: mail
}, onSend);
} else {
// send email as plaintext
self._emailDao.sendPlaintext({
email: mail
}, onSend);
}
function onSend(err) {
if (err) {
self._outboxBusy = false;
if (err.code === 42) {
// offline try again later
done();
} else {
self._outboxBusy = false;
callback(err);
}
return;
}
// remove the pending mail from the storage
removeFromStorage(mail, done);
// fire sent notification
if (typeof self.onSent === 'function') {
self.onSent(mail);
}
}
}
// removes the mail object from disk after successfully sending it
function removeFromStorage(mail, done) {
self._devicestorage.removeList(outboxDb + '_' + mail.uid, function(err) {
if (err) {
self._outboxBusy = false;
callback(err);
return;
}
// if we're not online, don't even bother sending mails.
if (!self._emailDao._account.online || _.isEmpty(pendingMails)) {
self._outboxBusy = false;
callback(null, pendingMails.length);
return;
}
// we're done after all the mails have been handled
// update the outbox count...
var after = _.after(pendingMails.length, function() {
self._outboxBusy = false;
callback(null, unsentMails);
});
// send pending mails if possible
pendingMails.forEach(function(mail) {
send(mail, after);
});
done();
});
}
};
// send the message
function send(mail, done) {
// check is email is to be sent encrypted or as plaintex
if (mail.encrypted === true) {
// email was already encrypted before persisting in outbox, tell pgpmailer to send encrypted and not encrypt again
self._emailDao.sendEncrypted({
email: mail
}, onSend);
} else {
// send email as plaintext
self._emailDao.sendPlaintext({
email: mail
}, onSend);
}
function onSend(err) {
if (err) {
self._outboxBusy = false;
if (err.code === 42) {
// offline try again later
done();
} else {
self._outboxBusy = false;
callback(err);
}
return;
}
// remove the pending mail from the storage
removeFromStorage(mail, done);
// fire sent notification
if (typeof self.onSent === 'function') {
self.onSent(mail);
}
}
}
// removes the mail object from disk after successfully sending it
function removeFromStorage(mail, done) {
self._devicestorage.removeList(outboxDb + '_' + mail.uid, function(err) {
if (err) {
self._outboxBusy = false;
callback(err);
return;
}
done();
});
}
};
return OutboxBO;
});
module.exports = OutboxBO;

View File

@ -1,31 +1,29 @@
define(function(require) {
'use strict';
'use strict';
var cfg = require('js/app-config').config;
var cfg = require('../app-config').config;
//
// Controller
//
//
// Controller
//
var AboutCtrl = function($scope) {
var AboutCtrl = function($scope) {
$scope.state.about = {
toggle: function(to) {
$scope.state.lightbox = (to) ? 'about' : undefined;
}
};
//
// scope variables
//
$scope.version = cfg.appVersion;
$scope.date = new Date();
//
// scope functions
//
$scope.state.about = {
toggle: function(to) {
$scope.state.lightbox = (to) ? 'about' : undefined;
}
};
return AboutCtrl;
});
//
// scope variables
//
$scope.version = cfg.appVersion;
$scope.date = new Date();
//
// scope functions
//
};
module.exports = AboutCtrl;

View File

@ -1,61 +1,59 @@
define(function(require) {
'use strict';
'use strict';
var appController = require('js/app-controller'),
dl = require('js/util/download'),
config = require('js/app-config').config,
pgp, keychain, userId;
var appController = require('../app-controller'),
dl = require('../util/download'),
config = require('../app-config').config,
pgp, keychain, userId;
//
// Controller
//
//
// Controller
//
var AccountCtrl = function($scope) {
userId = appController._emailDao._account.emailAddress;
keychain = appController._keychain;
pgp = appController._pgp;
var AccountCtrl = function($scope) {
userId = appController._emailDao._account.emailAddress;
keychain = appController._keychain;
pgp = appController._pgp;
$scope.state.account = {
toggle: function(to) {
$scope.state.lightbox = (to) ? 'account' : undefined;
}
};
//
// scope variables
//
var keyParams = pgp.getKeyParams();
$scope.eMail = userId;
$scope.keyId = keyParams._id.slice(8);
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 functions
//
$scope.exportKeyFile = function() {
keychain.getUserKeyPair(userId, function(err, keys) {
if (err) {
$scope.onError(err);
return;
}
var keyId = keys.publicKey._id;
var file = 'whiteout_mail_' + userId + '_' + keyId.substring(8, keyId.length);
dl.createDownload({
content: keys.publicKey.publicKey + '\r\n' + keys.privateKey.encryptedKey,
filename: file + '.asc',
contentType: 'text/plain'
});
});
};
$scope.state.account = {
toggle: function(to) {
$scope.state.lightbox = (to) ? 'account' : undefined;
}
};
return AccountCtrl;
});
//
// scope variables
//
var keyParams = pgp.getKeyParams();
$scope.eMail = userId;
$scope.keyId = keyParams._id.slice(8);
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 functions
//
$scope.exportKeyFile = function() {
keychain.getUserKeyPair(userId, function(err, keys) {
if (err) {
$scope.onError(err);
return;
}
var keyId = keys.publicKey._id;
var file = 'whiteout_mail_' + userId + '_' + keyId.substring(8, keyId.length);
dl.createDownload({
content: keys.publicKey.publicKey + '\r\n' + keys.privateKey.encryptedKey,
filename: file + '.asc',
contentType: 'text/plain'
});
});
};
};
module.exports = AccountCtrl;

View File

@ -1,120 +1,118 @@
define(function(require) {
'use strict';
'use strict';
var appCtrl = require('js/app-controller'),
cfg = require('js/app-config').config;
var appCtrl = require('../app-controller'),
cfg = require('../app-config').config;
var AddAccountCtrl = function($scope, $location, $routeParams) {
if (!appCtrl._auth && !$routeParams.dev) {
$location.path('/'); // init app
var AddAccountCtrl = function($scope, $location, $routeParams) {
if (!appCtrl._auth && !$routeParams.dev) {
$location.path('/'); // init app
return;
}
$scope.step = 1;
$scope.goTo = function(step) {
$scope.step = step;
};
$scope.createWhiteoutAccount = function() {
if ($scope.form.$invalid) {
return;
}
$scope.step = 1;
$scope.busy = true;
$scope.errMsg = undefined; // reset error msg
$scope.emailAddress = $scope.user + '@' + cfg.wmailDomain;
$scope.goTo = function(step) {
$scope.step = step;
};
// call REST api
appCtrl._adminDao.createUser({
emailAddress: $scope.emailAddress,
password: $scope.pass,
phone: $scope.phone.replace(/\s+/g, ''), // remove spaces from the phone number
betaCode: $scope.betaCode.toUpperCase()
}, function(err) {
$scope.busy = false;
$scope.createWhiteoutAccount = function() {
if ($scope.form.$invalid) {
return;
}
$scope.busy = true;
$scope.errMsg = undefined; // reset error msg
$scope.emailAddress = $scope.user + '@' + cfg.wmailDomain;
// call REST api
appCtrl._adminDao.createUser({
emailAddress: $scope.emailAddress,
password: $scope.pass,
phone: $scope.phone.replace(/\s+/g, ''), // remove spaces from the phone number
betaCode: $scope.betaCode.toUpperCase()
}, function(err) {
$scope.busy = false;
if (err) {
$scope.errMsg = err.errMsg || err.message;
$scope.$apply();
return;
}
$scope.goTo(3);
if (err) {
$scope.errMsg = err.errMsg || err.message;
$scope.$apply();
});
};
$scope.validateUser = function() {
if ($scope.formValidate.$invalid) {
return;
}
$scope.busyValidate = true;
$scope.errMsgValidate = undefined; // reset error msg
// verify user to REST api
appCtrl._adminDao.validateUser({
emailAddress: $scope.emailAddress,
token: $scope.token.toUpperCase()
}, function(err) {
if (err) {
$scope.busyValidate = false;
$scope.errMsgValidate = err.errMsg || err.message;
$scope.$apply();
return;
}
// proceed to login
$scope.login();
});
};
$scope.login = function() {
// store credentials in memory
appCtrl._auth.setCredentials({
provider: 'wmail',
emailAddress: $scope.emailAddress,
username: $scope.emailAddress,
realname: $scope.realname,
password: $scope.pass,
imap: cfg.wmail.imap,
smtp: cfg.wmail.smtp
});
// proceed to login and keygen
$location.path('/login');
$scope.goTo(3);
$scope.$apply();
};
$scope.connectToGoogle = function() {
// test for oauth support
if (appCtrl._auth._oauth.isSupported()) {
// fetches the email address from the chrome identity api
appCtrl._auth.getOAuthToken(function(err) {
if (err) {
return $scope.onError(err);
}
$location.path('/login-set-credentials').search({
provider: 'gmail'
});
$scope.$apply();
});
return;
}
// use normal user/password login
$location.path('/login-set-credentials').search({
provider: 'gmail'
});
};
$scope.connectTo = function(provider) {
$location.path('/login-set-credentials').search({
provider: provider
});
};
});
};
return AddAccountCtrl;
});
$scope.validateUser = function() {
if ($scope.formValidate.$invalid) {
return;
}
$scope.busyValidate = true;
$scope.errMsgValidate = undefined; // reset error msg
// verify user to REST api
appCtrl._adminDao.validateUser({
emailAddress: $scope.emailAddress,
token: $scope.token.toUpperCase()
}, function(err) {
if (err) {
$scope.busyValidate = false;
$scope.errMsgValidate = err.errMsg || err.message;
$scope.$apply();
return;
}
// proceed to login
$scope.login();
});
};
$scope.login = function() {
// store credentials in memory
appCtrl._auth.setCredentials({
provider: 'wmail',
emailAddress: $scope.emailAddress,
username: $scope.emailAddress,
realname: $scope.realname,
password: $scope.pass,
imap: cfg.wmail.imap,
smtp: cfg.wmail.smtp
});
// proceed to login and keygen
$location.path('/login');
$scope.$apply();
};
$scope.connectToGoogle = function() {
// test for oauth support
if (appCtrl._auth._oauth.isSupported()) {
// fetches the email address from the chrome identity api
appCtrl._auth.getOAuthToken(function(err) {
if (err) {
return $scope.onError(err);
}
$location.path('/login-set-credentials').search({
provider: 'gmail'
});
$scope.$apply();
});
return;
}
// use normal user/password login
$location.path('/login-set-credentials').search({
provider: 'gmail'
});
};
$scope.connectTo = function(provider) {
$location.path('/login-set-credentials').search({
provider: provider
});
};
};
module.exports = AddAccountCtrl;

View File

@ -1,143 +1,139 @@
define(function(require) {
'use strict';
'use strict';
var angular = require('angular'),
_ = require('underscore'),
appController = require('js/app-controller'),
keychain, pgp;
var appController = require('../app-controller'),
keychain, pgp;
//
// Controller
//
//
// Controller
//
var ContactsCtrl = function($scope) {
keychain = appController._keychain,
pgp = appController._pgp;
var ContactsCtrl = function($scope) {
keychain = appController._keychain,
pgp = appController._pgp;
$scope.state.contacts = {
toggle: function(to) {
$scope.state.lightbox = (to) ? 'contacts' : undefined;
$scope.state.contacts = {
toggle: function(to) {
$scope.state.lightbox = (to) ? 'contacts' : undefined;
$scope.listKeys();
}
};
// set default value so that the popover height is correct on init
$scope.fingerprint = 'XXXX XXXX XXXX XXXX XXXX XXXX XXXX XXXX XXXX XXXX';
//
// scope functions
//
$scope.listKeys = function() {
keychain.listLocalPublicKeys(function(err, keys) {
if (err) {
$scope.onError(err);
return;
}
keys.forEach(addParams);
$scope.keys = keys;
$scope.$apply();
function addParams(key) {
var params = pgp.getKeyParams(key.publicKey);
_.extend(key, params);
}
});
};
$scope.getFingerprint = function(key) {
var fpr = key.fingerprint;
var formatted = 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.fingerprint = formatted;
};
$scope.importKey = function(publicKeyArmored) {
var keyParams, pubkey;
// verifiy public key string
if (publicKeyArmored.indexOf('-----BEGIN PGP PUBLIC KEY BLOCK-----') < 0) {
$scope.onError({
showBugReporter: false,
message: 'Invalid public key!'
});
return;
}
try {
keyParams = pgp.getKeyParams(publicKeyArmored);
} catch (e) {
$scope.onError(new Error('Error reading public key params!'));
return;
}
pubkey = {
_id: keyParams._id,
userId: keyParams.userId,
userIds: keyParams.userIds,
publicKey: publicKeyArmored,
imported: true // mark manually imported keys
};
keychain.saveLocalPublicKey(pubkey, function(err) {
if (err) {
$scope.onError(err);
return;
}
// update displayed keys
$scope.listKeys();
});
};
$scope.removeKey = function(key) {
keychain.removeLocalPublicKey(key._id, function(err) {
if (err) {
$scope.onError(err);
return;
}
// update displayed keys
$scope.listKeys();
});
};
$scope.listKeys();
}
};
// set default value so that the popover height is correct on init
$scope.fingerprint = 'XXXX XXXX XXXX XXXX XXXX XXXX XXXX XXXX XXXX XXXX';
//
// Directives
// scope functions
//
var ngModule = angular.module('contacts', []);
ngModule.directive('keyfileInput', function() {
return function(scope, elm) {
elm.on('change', function(e) {
for (var i = 0; i < e.target.files.length; i++) {
importKey(e.target.files.item(i));
}
});
function importKey(file) {
var reader = new FileReader();
reader.onload = function(e) {
scope.importKey(e.target.result);
};
reader.readAsText(file);
$scope.listKeys = function() {
keychain.listLocalPublicKeys(function(err, keys) {
if (err) {
$scope.onError(err);
return;
}
};
});
ngModule.directive('keyfileBtn', function() {
return function(scope, elm) {
elm.on('click touchstart', function(e) {
e.preventDefault();
document.querySelector('#keyfile-input').click();
keys.forEach(addParams);
$scope.keys = keys;
$scope.$apply();
function addParams(key) {
var params = pgp.getKeyParams(key.publicKey);
_.extend(key, params);
}
});
};
$scope.getFingerprint = function(key) {
var fpr = key.fingerprint;
var formatted = 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.fingerprint = formatted;
};
$scope.importKey = function(publicKeyArmored) {
var keyParams, pubkey;
// verifiy public key string
if (publicKeyArmored.indexOf('-----BEGIN PGP PUBLIC KEY BLOCK-----') < 0) {
$scope.onError({
showBugReporter: false,
message: 'Invalid public key!'
});
};
});
return;
}
return ContactsCtrl;
});
try {
keyParams = pgp.getKeyParams(publicKeyArmored);
} catch (e) {
$scope.onError(new Error('Error reading public key params!'));
return;
}
pubkey = {
_id: keyParams._id,
userId: keyParams.userId,
userIds: keyParams.userIds,
publicKey: publicKeyArmored,
imported: true // mark manually imported keys
};
keychain.saveLocalPublicKey(pubkey, function(err) {
if (err) {
$scope.onError(err);
return;
}
// update displayed keys
$scope.listKeys();
});
};
$scope.removeKey = function(key) {
keychain.removeLocalPublicKey(key._id, function(err) {
if (err) {
$scope.onError(err);
return;
}
// update displayed keys
$scope.listKeys();
});
};
};
//
// Directives
//
var ngModule = angular.module('contacts', []);
ngModule.directive('keyfileInput', function() {
return function(scope, elm) {
elm.on('change', function(e) {
for (var i = 0; i < e.target.files.length; i++) {
importKey(e.target.files.item(i));
}
});
function importKey(file) {
var reader = new FileReader();
reader.onload = function(e) {
scope.importKey(e.target.result);
};
reader.readAsText(file);
}
};
});
ngModule.directive('keyfileBtn', function() {
return function(scope, elm) {
elm.on('click touchstart', function(e) {
e.preventDefault();
document.querySelector('#keyfile-input').click();
});
};
});
module.exports = ContactsCtrl;

View File

@ -1,16 +1,14 @@
define(function() {
'use strict';
'use strict';
var DialogCtrl = function($scope) {
$scope.confirm = function(ok) {
$scope.state.dialog.open = false;
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;
};
if ($scope.state.dialog.callback) {
$scope.state.dialog.callback(ok);
}
$scope.state.dialog.callback = undefined;
};
};
return DialogCtrl;
});
module.exports = DialogCtrl;

View File

@ -1,73 +1,71 @@
define(function(require) {
'use strict';
'use strict';
var appController = require('js/app-controller');
var appController = require('../app-controller');
var LoginExistingCtrl = function($scope, $location, $routeParams) {
if (!appController._emailDao && !$routeParams.dev) {
$location.path('/'); // init app
var LoginExistingCtrl = function($scope, $location, $routeParams) {
if (!appController._emailDao && !$routeParams.dev) {
$location.path('/'); // init app
return;
}
var emailDao = appController._emailDao;
$scope.buttonEnabled = true;
$scope.incorrect = false;
$scope.change = function() {
$scope.incorrect = false;
};
$scope.confirmPassphrase = function() {
if (!$scope.passphrase) {
return;
}
var emailDao = appController._emailDao;
$scope.buttonEnabled = true;
// disable button once loggin has started
$scope.buttonEnabled = false;
$scope.incorrect = false;
$scope.change = function() {
$scope.incorrect = false;
};
$scope.confirmPassphrase = function() {
if (!$scope.passphrase) {
return;
}
// disable button once loggin has started
$scope.buttonEnabled = false;
$scope.incorrect = false;
unlockCrypto();
};
function unlockCrypto() {
var userId = emailDao._account.emailAddress;
emailDao._keychain.getUserKeyPair(userId, function(err, keypair) {
if (err) {
handleError(err);
return;
}
emailDao.unlock({
keypair: keypair,
passphrase: $scope.passphrase
}, onUnlock);
});
}
function onUnlock(err) {
if (err) {
$scope.incorrect = true;
$scope.buttonEnabled = true;
$scope.$apply();
return;
}
appController._auth.storeCredentials(function(err) {
if (err) {
return $scope.onError(err);
}
$location.path('/desktop');
$scope.$apply();
});
}
function handleError(err) {
$scope.incorrect = true;
$scope.buttonEnabled = true;
$scope.onError(err);
}
unlockCrypto();
};
return LoginExistingCtrl;
});
function unlockCrypto() {
var userId = emailDao._account.emailAddress;
emailDao._keychain.getUserKeyPair(userId, function(err, keypair) {
if (err) {
handleError(err);
return;
}
emailDao.unlock({
keypair: keypair,
passphrase: $scope.passphrase
}, onUnlock);
});
}
function onUnlock(err) {
if (err) {
$scope.incorrect = true;
$scope.buttonEnabled = true;
$scope.$apply();
return;
}
appController._auth.storeCredentials(function(err) {
if (err) {
return $scope.onError(err);
}
$location.path('/desktop');
$scope.$apply();
});
}
function handleError(err) {
$scope.incorrect = true;
$scope.buttonEnabled = true;
$scope.onError(err);
}
};
module.exports = LoginExistingCtrl;

View File

@ -1,121 +1,119 @@
define(function(require) {
'use strict';
'use strict';
var appController = require('js/app-controller');
var appController = require('../app-controller');
var LoginInitialCtrl = function($scope, $location, $routeParams) {
if (!appController._emailDao && !$routeParams.dev) {
$location.path('/'); // init app
var LoginInitialCtrl = function($scope, $location, $routeParams) {
if (!appController._emailDao && !$routeParams.dev) {
$location.path('/'); // init app
return;
}
var emailDao = appController._emailDao,
states, termsMsg = 'You must accept the Terms of Service to continue.';
states = {
IDLE: 1,
PROCESSING: 2,
DONE: 3
};
$scope.state.ui = states.IDLE; // initial state
//
// scope functions
//
/**
* Continue to key import screen
*/
$scope.importKey = function() {
if (!$scope.state.agree) {
$scope.onError({
message: termsMsg
});
return;
}
var emailDao = appController._emailDao,
states, termsMsg = 'You must accept the Terms of Service to continue.';
states = {
IDLE: 1,
PROCESSING: 2,
DONE: 3
};
$scope.state.ui = states.IDLE; // initial state
//
// scope functions
//
/**
* Continue to key import screen
*/
$scope.importKey = function() {
if (!$scope.state.agree) {
$scope.onError({
message: termsMsg
});
return;
}
// sing up to newsletter
$scope.signUpToNewsletter();
// go to key import
$location.path('/login-new-device');
};
/**
* Continue to keygen
*/
$scope.generateKey = function() {
if (!$scope.state.agree) {
$scope.onError({
message: termsMsg
});
return;
}
// sing up to newsletter
$scope.signUpToNewsletter();
// go to set keygen screen
$scope.setState(states.PROCESSING);
setTimeout(function() {
emailDao.unlock({
passphrase: undefined // generate key without passphrase
}, function(err) {
if (err) {
$scope.setState(states.IDLE);
$scope.onError(err);
return;
}
appController._auth.storeCredentials(function(err) {
if (err) {
return $scope.onError(err);
}
$location.path('/desktop');
$scope.$apply();
});
});
}, 500);
};
/**
* [signUpToNewsletter description]
* @param {Function} callback (optional)
*/
$scope.signUpToNewsletter = function(callback) {
if (!$scope.state.newsletter) {
return;
}
var address = emailDao._account.emailAddress;
var uri = 'https://whiteout.us8.list-manage.com/subscribe/post?u=52ea5a9e1be9e1d194f184158&id=6538e8f09f';
var formData = new FormData();
formData.append('EMAIL', address);
formData.append('b_52ea5a9e1be9e1d194f184158_6538e8f09f', '');
var xhr = new XMLHttpRequest();
xhr.open('post', uri, true);
xhr.onload = function() {
if (callback) {
callback(null, xhr);
}
};
xhr.onerror = function(err) {
if (callback) {
callback(err);
}
};
xhr.send(formData);
};
$scope.setState = function(state) {
$scope.state.ui = state;
};
// sing up to newsletter
$scope.signUpToNewsletter();
// go to key import
$location.path('/login-new-device');
};
return LoginInitialCtrl;
});
/**
* Continue to keygen
*/
$scope.generateKey = function() {
if (!$scope.state.agree) {
$scope.onError({
message: termsMsg
});
return;
}
// sing up to newsletter
$scope.signUpToNewsletter();
// go to set keygen screen
$scope.setState(states.PROCESSING);
setTimeout(function() {
emailDao.unlock({
passphrase: undefined // generate key without passphrase
}, function(err) {
if (err) {
$scope.setState(states.IDLE);
$scope.onError(err);
return;
}
appController._auth.storeCredentials(function(err) {
if (err) {
return $scope.onError(err);
}
$location.path('/desktop');
$scope.$apply();
});
});
}, 500);
};
/**
* [signUpToNewsletter description]
* @param {Function} callback (optional)
*/
$scope.signUpToNewsletter = function(callback) {
if (!$scope.state.newsletter) {
return;
}
var address = emailDao._account.emailAddress;
var uri = 'https://whiteout.us8.list-manage.com/subscribe/post?u=52ea5a9e1be9e1d194f184158&id=6538e8f09f';
var formData = new FormData();
formData.append('EMAIL', address);
formData.append('b_52ea5a9e1be9e1d194f184158_6538e8f09f', '');
var xhr = new XMLHttpRequest();
xhr.open('post', uri, true);
xhr.onload = function() {
if (callback) {
callback(null, xhr);
}
};
xhr.onerror = function(err) {
if (callback) {
callback(err);
}
};
xhr.send(formData);
};
$scope.setState = function(state) {
$scope.state.ui = state;
};
};
module.exports = LoginInitialCtrl;

View File

@ -1,141 +1,138 @@
define(function(require) {
'use strict';
'use strict';
var angular = require('angular'),
appController = require('js/app-controller');
var appController = require('../app-controller');
var LoginExistingCtrl = function($scope, $location, $routeParams) {
if (!appController._emailDao && !$routeParams.dev) {
$location.path('/'); // init app
return;
}
var LoginExistingCtrl = function($scope, $location, $routeParams) {
if (!appController._emailDao && !$routeParams.dev) {
$location.path('/'); // init app
return;
}
var emailDao = appController._emailDao,
pgp = appController._pgp;
var emailDao = appController._emailDao,
pgp = appController._pgp;
$scope.incorrect = false;
$scope.confirmPassphrase = function() {
$scope.incorrect = false;
unlockCrypto();
};
$scope.confirmPassphrase = function() {
$scope.incorrect = false;
unlockCrypto();
};
function unlockCrypto() {
var userId = emailDao._account.emailAddress;
// check if user already has a public key on the key server
emailDao._keychain.getUserKeyPair(userId, function(err, keypair) {
if (err) {
$scope.onError(err);
return;
}
keypair = keypair || {};
// extract public key from private key block if missing in key file
if (!$scope.key.publicKeyArmored || $scope.key.publicKeyArmored.indexOf('-----BEGIN PGP PUBLIC KEY BLOCK-----') < 0) {
try {
$scope.key.publicKeyArmored = pgp.extractPublicKey($scope.key.privateKeyArmored);
} catch (e) {
$scope.onError(new Error('Error parsing public key from private key!'));
return;
}
}
// parse keypair params
var privKeyParams, pubKeyParams;
try {
privKeyParams = pgp.getKeyParams($scope.key.privateKeyArmored);
pubKeyParams = pgp.getKeyParams($scope.key.publicKeyArmored);
} catch (e) {
$scope.onError(new Error('Error reading key params!'));
return;
}
// set parsed private key
keypair.privateKey = {
_id: privKeyParams._id,
userId: userId,
userIds: privKeyParams.userIds,
encryptedKey: $scope.key.privateKeyArmored
};
if (!keypair.publicKey) {
// there is no public key on the key server yet... use parsed
keypair.publicKey = {
_id: pubKeyParams._id,
userId: userId,
userIds: pubKeyParams.userIds,
publicKey: $scope.key.publicKeyArmored
};
}
// import and validate keypair
emailDao.unlock({
keypair: keypair,
passphrase: $scope.passphrase
}, function(err) {
if (err) {
$scope.incorrect = true;
$scope.onError(err);
return;
}
emailDao._keychain.putUserKeyPair(keypair, onUnlock);
});
});
}
function onUnlock(err) {
function unlockCrypto() {
var userId = emailDao._account.emailAddress;
// check if user already has a public key on the key server
emailDao._keychain.getUserKeyPair(userId, function(err, keypair) {
if (err) {
$scope.onError(err);
return;
}
appController._auth.storeCredentials(function(err) {
if (err) {
return $scope.onError(err);
keypair = keypair || {};
// extract public key from private key block if missing in key file
if (!$scope.key.publicKeyArmored || $scope.key.publicKeyArmored.indexOf('-----BEGIN PGP PUBLIC KEY BLOCK-----') < 0) {
try {
$scope.key.publicKeyArmored = pgp.extractPublicKey($scope.key.privateKeyArmored);
} catch (e) {
$scope.onError(new Error('Error parsing public key from private key!'));
return;
}
}
$location.path('/desktop');
$scope.$apply();
});
}
};
// parse keypair params
var privKeyParams, pubKeyParams;
try {
privKeyParams = pgp.getKeyParams($scope.key.privateKeyArmored);
pubKeyParams = pgp.getKeyParams($scope.key.publicKeyArmored);
} catch (e) {
$scope.onError(new Error('Error reading key params!'));
return;
}
var ngModule = angular.module('login-new-device', []);
ngModule.directive('fileReader', function() {
return function(scope, elm) {
elm.bind('change', function(e) {
var files = e.target.files,
reader = new FileReader();
// set parsed private key
keypair.privateKey = {
_id: privKeyParams._id,
userId: userId,
userIds: privKeyParams.userIds,
encryptedKey: $scope.key.privateKeyArmored
};
if (files.length === 0) {
if (!keypair.publicKey) {
// there is no public key on the key server yet... use parsed
keypair.publicKey = {
_id: pubKeyParams._id,
userId: userId,
userIds: pubKeyParams.userIds,
publicKey: $scope.key.publicKeyArmored
};
}
// import and validate keypair
emailDao.unlock({
keypair: keypair,
passphrase: $scope.passphrase
}, function(err) {
if (err) {
$scope.incorrect = true;
$scope.onError(err);
return;
}
reader.onload = function(e) {
var rawKeys = e.target.result,
index = rawKeys.indexOf('-----BEGIN PGP PRIVATE KEY BLOCK-----'),
keyParts;
if (index === -1) {
scope.onError(new Error('Error parsing private PGP key block!'));
return;
}
keyParts = {
publicKeyArmored: rawKeys.substring(0, index).trim(),
privateKeyArmored: rawKeys.substring(index, rawKeys.length).trim()
};
scope.$apply(function() {
scope.key = keyParts;
});
};
reader.readAsText(files[0]);
emailDao._keychain.putUserKeyPair(keypair, onUnlock);
});
};
});
});
}
return LoginExistingCtrl;
});
function onUnlock(err) {
if (err) {
$scope.onError(err);
return;
}
appController._auth.storeCredentials(function(err) {
if (err) {
return $scope.onError(err);
}
$location.path('/desktop');
$scope.$apply();
});
}
};
var ngModule = angular.module('login-new-device', []);
ngModule.directive('fileReader', function() {
return function(scope, elm) {
elm.bind('change', function(e) {
var files = e.target.files,
reader = new FileReader();
if (files.length === 0) {
return;
}
reader.onload = function(e) {
var rawKeys = e.target.result,
index = rawKeys.indexOf('-----BEGIN PGP PRIVATE KEY BLOCK-----'),
keyParts;
if (index === -1) {
scope.onError(new Error('Error parsing private PGP key block!'));
return;
}
keyParts = {
publicKeyArmored: rawKeys.substring(0, index).trim(),
privateKeyArmored: rawKeys.substring(index, rawKeys.length).trim()
};
scope.$apply(function() {
scope.key = keyParts;
});
};
reader.readAsText(files[0]);
});
};
});
module.exports = LoginExistingCtrl;

View File

@ -1,134 +1,132 @@
define(function(require) {
'use strict';
'use strict';
var appController = require('js/app-controller');
var appController = require('../app-controller');
var LoginPrivateKeyDownloadCtrl = function($scope, $location, $routeParams) {
if (!appController._emailDao && !$routeParams.dev) {
$location.path('/'); // init app
var LoginPrivateKeyDownloadCtrl = function($scope, $location, $routeParams) {
if (!appController._emailDao && !$routeParams.dev) {
$location.path('/'); // init app
return;
}
var keychain = appController._keychain,
emailDao = appController._emailDao,
userId = emailDao._account.emailAddress;
$scope.step = 1;
$scope.handlePaste = function(event) {
var evt = event;
if (evt.originalEvent) {
evt = evt.originalEvent;
}
var value = evt.clipboardData.getData('text/plain');
if (!value) {
return;
}
var keychain = appController._keychain,
emailDao = appController._emailDao,
userId = emailDao._account.emailAddress;
$scope.step = 1;
$scope.handlePaste = function(event) {
var evt = event;
if (evt.originalEvent) {
evt = evt.originalEvent;
}
var value = evt.clipboardData.getData('text/plain');
if (!value) {
return;
}
value = value.replace(/-/g, '');
$scope.code0 = value.slice(0, 4);
$scope.code1 = value.slice(4, 8);
$scope.code2 = value.slice(8, 12);
$scope.code3 = value.slice(12, 16);
$scope.code4 = value.slice(16, 20);
$scope.code5 = value.slice(20, 24);
};
$scope.verifyRecoveryToken = function(callback) {
if (!$scope.recoveryToken) {
$scope.onError(new Error('Please set the recovery token!'));
return;
}
keychain.getUserKeyPair(userId, function(err, keypair) {
if (err) {
$scope.onError(err);
return;
}
// remember for storage later
$scope.cachedKeypair = keypair;
keychain.downloadPrivateKey({
userId: userId,
keyId: keypair.publicKey._id,
recoveryToken: $scope.recoveryToken.toUpperCase()
}, function(err, encryptedPrivateKey) {
if (err) {
$scope.onError(err);
return;
}
$scope.encryptedPrivateKey = encryptedPrivateKey;
callback();
});
});
};
$scope.decryptAndStorePrivateKeyLocally = function() {
var inputCode = '' + $scope.code0 + $scope.code1 + $scope.code2 + $scope.code3 + $scope.code4 + $scope.code5;
if (!inputCode) {
$scope.onError(new Error('Please enter the keychain code!'));
return;
}
var options = $scope.encryptedPrivateKey;
options.code = inputCode.toUpperCase();
keychain.decryptAndStorePrivateKeyLocally(options, function(err, privateKey) {
if (err) {
$scope.onError(err);
return;
}
// add private key to cached keypair object
$scope.cachedKeypair.privateKey = privateKey;
// try empty passphrase
emailDao.unlock({
keypair: $scope.cachedKeypair,
passphrase: undefined
}, function(err) {
if (err) {
// go to passphrase login screen
$scope.goTo('/login-existing');
return;
}
// passphrase is corrent ... go to main app
appController._auth.storeCredentials(function(err) {
if (err) {
return $scope.onError(err);
}
$scope.goTo('/desktop');
});
});
});
};
$scope.goForward = function() {
if ($scope.step === 1) {
$scope.verifyRecoveryToken(function() {
$scope.step++;
$scope.$apply();
});
return;
}
if ($scope.step === 2) {
$scope.decryptAndStorePrivateKeyLocally();
return;
}
};
$scope.goTo = function(location) {
$location.path(location);
$scope.$apply();
};
value = value.replace(/-/g, '');
$scope.code0 = value.slice(0, 4);
$scope.code1 = value.slice(4, 8);
$scope.code2 = value.slice(8, 12);
$scope.code3 = value.slice(12, 16);
$scope.code4 = value.slice(16, 20);
$scope.code5 = value.slice(20, 24);
};
return LoginPrivateKeyDownloadCtrl;
});
$scope.verifyRecoveryToken = function(callback) {
if (!$scope.recoveryToken) {
$scope.onError(new Error('Please set the recovery token!'));
return;
}
keychain.getUserKeyPair(userId, function(err, keypair) {
if (err) {
$scope.onError(err);
return;
}
// remember for storage later
$scope.cachedKeypair = keypair;
keychain.downloadPrivateKey({
userId: userId,
keyId: keypair.publicKey._id,
recoveryToken: $scope.recoveryToken.toUpperCase()
}, function(err, encryptedPrivateKey) {
if (err) {
$scope.onError(err);
return;
}
$scope.encryptedPrivateKey = encryptedPrivateKey;
callback();
});
});
};
$scope.decryptAndStorePrivateKeyLocally = function() {
var inputCode = '' + $scope.code0 + $scope.code1 + $scope.code2 + $scope.code3 + $scope.code4 + $scope.code5;
if (!inputCode) {
$scope.onError(new Error('Please enter the keychain code!'));
return;
}
var options = $scope.encryptedPrivateKey;
options.code = inputCode.toUpperCase();
keychain.decryptAndStorePrivateKeyLocally(options, function(err, privateKey) {
if (err) {
$scope.onError(err);
return;
}
// add private key to cached keypair object
$scope.cachedKeypair.privateKey = privateKey;
// try empty passphrase
emailDao.unlock({
keypair: $scope.cachedKeypair,
passphrase: undefined
}, function(err) {
if (err) {
// go to passphrase login screen
$scope.goTo('/login-existing');
return;
}
// passphrase is corrent ... go to main app
appController._auth.storeCredentials(function(err) {
if (err) {
return $scope.onError(err);
}
$scope.goTo('/desktop');
});
});
});
};
$scope.goForward = function() {
if ($scope.step === 1) {
$scope.verifyRecoveryToken(function() {
$scope.step++;
$scope.$apply();
});
return;
}
if ($scope.step === 2) {
$scope.decryptAndStorePrivateKeyLocally();
return;
}
};
$scope.goTo = function(location) {
$location.path(location);
$scope.$apply();
};
};
module.exports = LoginPrivateKeyDownloadCtrl;

View File

@ -1,120 +1,118 @@
define(function(require) {
'use strict';
'use strict';
var ENCRYPTION_METHOD_NONE = 0;
var ENCRYPTION_METHOD_STARTTLS = 1;
var ENCRYPTION_METHOD_TLS = 2;
var ENCRYPTION_METHOD_NONE = 0;
var ENCRYPTION_METHOD_STARTTLS = 1;
var ENCRYPTION_METHOD_TLS = 2;
var appCtrl = require('js/app-controller'),
config = require('js/app-config').config;
var appCtrl = require('../app-controller'),
config = require('../app-config').config;
var SetCredentialsCtrl = function($scope, $location, $routeParams) {
if (!appCtrl._auth && !$routeParams.dev) {
$location.path('/'); // init app
return;
var SetCredentialsCtrl = function($scope, $location, $routeParams) {
if (!appCtrl._auth && !$routeParams.dev) {
$location.path('/'); // init app
return;
}
var auth = appCtrl._auth;
var doctor = appCtrl._doctor;
//
// Presets and Settings
//
var provider = $location.search().provider;
$scope.hasProviderPreset = !!config[provider];
$scope.useOAuth = !!auth.oauthToken;
$scope.showDetails = (provider === 'custom');
if ($scope.useOAuth) {
$scope.emailAddress = auth.emailAddress;
}
if ($scope.hasProviderPreset) {
// use non-editable presets
// SMTP config
$scope.smtpHost = config[provider].smtp.host;
$scope.smtpPort = config[provider].smtp.port;
$scope.smtpCert = config[provider].smtp.ca;
$scope.smtpPinned = config[provider].smtp.pinned;
// transport encryption method
if (config[provider].smtp.secure && !config[provider].smtp.ignoreTLS) {
$scope.smtpEncryption = ENCRYPTION_METHOD_TLS;
} else if (!config[provider].smtp.secure && !config[provider].smtp.ignoreTLS) {
$scope.smtpEncryption = ENCRYPTION_METHOD_STARTTLS;
} else {
$scope.smtpEncryption = ENCRYPTION_METHOD_NONE;
}
var auth = appCtrl._auth;
var doctor = appCtrl._doctor;
// IMAP config
$scope.imapHost = config[provider].imap.host;
$scope.imapPort = config[provider].imap.port;
$scope.imapCert = config[provider].imap.ca;
$scope.imapPinned = config[provider].imap.pinned;
//
// Presets and Settings
//
var provider = $location.search().provider;
$scope.hasProviderPreset = !!config[provider];
$scope.useOAuth = !!auth.oauthToken;
$scope.showDetails = (provider === 'custom');
if ($scope.useOAuth) {
$scope.emailAddress = auth.emailAddress;
// transport encryption method
if (config[provider].imap.secure && !config[provider].imap.ignoreTLS) {
$scope.imapEncryption = ENCRYPTION_METHOD_TLS;
} else if (!config[provider].imap.secure && !config[provider].imap.ignoreTLS) {
$scope.imapEncryption = ENCRYPTION_METHOD_STARTTLS;
} else {
$scope.imapEncryption = ENCRYPTION_METHOD_NONE;
}
}
if ($scope.hasProviderPreset) {
// use non-editable presets
// SMTP config
$scope.smtpHost = config[provider].smtp.host;
$scope.smtpPort = config[provider].smtp.port;
$scope.smtpCert = config[provider].smtp.ca;
$scope.smtpPinned = config[provider].smtp.pinned;
$scope.test = function() {
// parse the <select> dropdown lists
var imapEncryption = parseInt($scope.imapEncryption, 10);
var smtpEncryption = parseInt($scope.smtpEncryption, 10);
// transport encryption method
if (config[provider].smtp.secure && !config[provider].smtp.ignoreTLS) {
$scope.smtpEncryption = ENCRYPTION_METHOD_TLS;
} else if (!config[provider].smtp.secure && !config[provider].smtp.ignoreTLS) {
$scope.smtpEncryption = ENCRYPTION_METHOD_STARTTLS;
} else {
$scope.smtpEncryption = ENCRYPTION_METHOD_NONE;
// build credentials object
var credentials = {
provider: provider,
emailAddress: $scope.emailAddress,
username: $scope.username || $scope.emailAddress,
realname: $scope.realname,
password: $scope.password,
xoauth2: auth.oauthToken,
imap: {
host: $scope.imapHost.toLowerCase(),
port: $scope.imapPort,
secure: imapEncryption === ENCRYPTION_METHOD_TLS,
ignoreTLS: imapEncryption === ENCRYPTION_METHOD_NONE,
ca: $scope.imapCert,
pinned: !!$scope.imapPinned
},
smtp: {
host: $scope.smtpHost.toLowerCase(),
port: $scope.smtpPort,
secure: smtpEncryption === ENCRYPTION_METHOD_TLS,
ignoreTLS: smtpEncryption === ENCRYPTION_METHOD_NONE,
ca: $scope.smtpCert,
pinned: !!$scope.smtpPinned
}
// IMAP config
$scope.imapHost = config[provider].imap.host;
$scope.imapPort = config[provider].imap.port;
$scope.imapCert = config[provider].imap.ca;
$scope.imapPinned = config[provider].imap.pinned;
// transport encryption method
if (config[provider].imap.secure && !config[provider].imap.ignoreTLS) {
$scope.imapEncryption = ENCRYPTION_METHOD_TLS;
} else if (!config[provider].imap.secure && !config[provider].imap.ignoreTLS) {
$scope.imapEncryption = ENCRYPTION_METHOD_STARTTLS;
} else {
$scope.imapEncryption = ENCRYPTION_METHOD_NONE;
}
}
$scope.test = function() {
// parse the <select> dropdown lists
var imapEncryption = parseInt($scope.imapEncryption, 10);
var smtpEncryption = parseInt($scope.smtpEncryption, 10);
// build credentials object
var credentials = {
provider: provider,
emailAddress: $scope.emailAddress,
username: $scope.username || $scope.emailAddress,
realname: $scope.realname,
password: $scope.password,
xoauth2: auth.oauthToken,
imap: {
host: $scope.imapHost.toLowerCase(),
port: $scope.imapPort,
secure: imapEncryption === ENCRYPTION_METHOD_TLS,
ignoreTLS: imapEncryption === ENCRYPTION_METHOD_NONE,
ca: $scope.imapCert,
pinned: !!$scope.imapPinned
},
smtp: {
host: $scope.smtpHost.toLowerCase(),
port: $scope.smtpPort,
secure: smtpEncryption === ENCRYPTION_METHOD_TLS,
ignoreTLS: smtpEncryption === ENCRYPTION_METHOD_NONE,
ca: $scope.smtpCert,
pinned: !!$scope.smtpPinned
}
};
// use the credentials in the connection doctor
doctor.configure(credentials);
// run connection doctor test suite
$scope.busy = true;
doctor.check(function(err) {
if (err) {
// display the error in the settings UI
$scope.connectionError = err;
} else {
// persists the credentials and forwards to /login
auth.setCredentials(credentials);
$location.path('/login');
}
$scope.busy = false;
$scope.$apply();
});
};
};
return SetCredentialsCtrl;
});
// use the credentials in the connection doctor
doctor.configure(credentials);
// run connection doctor test suite
$scope.busy = true;
doctor.check(function(err) {
if (err) {
// display the error in the settings UI
$scope.connectionError = err;
} else {
// persists the credentials and forwards to /login
auth.setCredentials(credentials);
$location.path('/login');
}
$scope.busy = false;
$scope.$apply();
});
};
};
module.exports = SetCredentialsCtrl;

View File

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

File diff suppressed because it is too large Load Diff

View File

@ -1,210 +1,206 @@
define(function(require) {
'use strict';
'use strict';
var angular = require('angular'),
appController = require('js/app-controller'),
config = require('js/app-config').config,
notification = require('js/util/notification'),
backBtnHandler = require('js/util/backbutton-handler'),
_ = require('underscore'),
emailDao, outboxBo;
var appController = require('../app-controller'),
config = require('../app-config').config,
notification = require('../util/notification'),
backBtnHandler = require('../util/backbutton-handler'),
emailDao, outboxBo;
//
// Controller
//
var NavigationCtrl = function($scope, $routeParams, $location) {
if (!appController._emailDao && !$routeParams.dev) {
$location.path('/'); // init app
return;
}
emailDao = appController._emailDao;
outboxBo = appController._outboxBo;
//
// Controller
// scope functions
//
var NavigationCtrl = function($scope, $routeParams, $location) {
if (!appController._emailDao && !$routeParams.dev) {
$location.path('/'); // init app
return;
}
emailDao = appController._emailDao;
outboxBo = appController._outboxBo;
//
// scope functions
//
$scope.state.nav = {
open: false,
toggle: function(to) {
this.open = to;
}
};
$scope.openFolder = function(folder) {
$scope.state.nav.currentFolder = folder;
$scope.state.nav.toggle(false);
};
$scope.onOutboxUpdate = function(err, count) {
if (err) {
$scope.onError(err);
return;
}
// update the outbox mail count
var outbox = _.findWhere($scope.account.folders, {
type: config.outboxMailboxType
});
outbox.count = count;
$scope.$apply();
emailDao.refreshFolder({
folder: outbox
}, $scope.onError);
};
$scope.logout = function() {
$scope.onError({
title: 'Logout',
message: 'Are you sure you want to logout?',
callback: function(confirm) {
if (confirm) {
appController.logout();
}
},
sync: true
});
};
//
// Start
//
// handle back button
backBtnHandler.start();
// init folders
initializeFolders();
// select inbox as the current folder on init
if ($scope.account.folders && $scope.account.folders.length > 0) {
$scope.openFolder($scope.account.folders[0]);
}
// connect imap/smtp clients on first startup
appController.onConnect(function(err) {
if (err) {
$scope.onError(err);
return;
}
// select inbox if not yet selected
if (!$scope.state.nav.currentFolder) {
$scope.openFolder($scope.account.folders[0]);
$scope.$apply();
}
});
//
// helper functions
//
function initializeFolders() {
// create dummy folder in dev environment only
if ($routeParams.dev) {
createDummyFolders();
return;
}
// get pointer to account/folder/message tree on root scope
$scope.$root.account = emailDao._account;
// set notificatio handler for sent messages
outboxBo.onSent = sentNotification;
// start checking outbox periodically
outboxBo.startChecking($scope.onOutboxUpdate);
}
function sentNotification(email) {
notification.create({
title: 'Message sent',
message: email.subject,
timeout: 2000
}, function() {});
}
// attach dummy folders for development
function createDummyFolders() {
$scope.$root.account = {};
$scope.account.folders = [{
type: 'Inbox',
count: 2,
path: 'INBOX'
}, {
type: 'Sent',
count: 0,
path: 'SENT'
}, {
type: config.outboxMailboxType,
count: 0,
path: config.outboxMailboxPath
}, {
type: 'Drafts',
count: 0,
path: 'DRAFTS'
}, {
type: 'Trash',
count: 0,
path: 'TRASH'
}];
$scope.state.nav = {
open: false,
toggle: function(to) {
this.open = to;
}
};
$scope.openFolder = function(folder) {
$scope.state.nav.currentFolder = folder;
$scope.state.nav.toggle(false);
};
$scope.onOutboxUpdate = function(err, count) {
if (err) {
$scope.onError(err);
return;
}
// update the outbox mail count
var outbox = _.findWhere($scope.account.folders, {
type: config.outboxMailboxType
});
outbox.count = count;
$scope.$apply();
emailDao.refreshFolder({
folder: outbox
}, $scope.onError);
};
$scope.logout = function() {
$scope.onError({
title: 'Logout',
message: 'Are you sure you want to logout?',
callback: function(confirm) {
if (confirm) {
appController.logout();
}
},
sync: true
});
};
//
// Directives
// Start
//
var ngModule = angular.module('navigation', []);
ngModule.directive('keyShortcuts', function($timeout) {
return function(scope, elm) {
elm.bind('keydown', function(e) {
// global state is not yet set, ignore keybaord shortcuts
if (!scope.state) {
return;
}
// handle back button
backBtnHandler.start();
// init folders
initializeFolders();
var modifier = e.ctrlKey || e.metaKey;
// select inbox as the current folder on init
if ($scope.account.folders && $scope.account.folders.length > 0) {
$scope.openFolder($scope.account.folders[0]);
}
// connect imap/smtp clients on first startup
appController.onConnect(function(err) {
if (err) {
$scope.onError(err);
return;
}
if (modifier && e.keyCode === 78 && scope.state.lightbox !== 'write') {
// n -> new mail
e.preventDefault();
scope.state.writer.write();
scope.$apply();
} else if (modifier && e.keyCode === 70 && scope.state.lightbox !== 'write') {
// f -> find
e.preventDefault();
scope.state.mailList.searching = true;
$timeout(function() {
scope.state.mailList.searching = false;
}, 200);
scope.$apply();
} else if (modifier && e.keyCode === 82 && scope.state.lightbox !== 'write' && scope.state.mailList.selected) {
// r -> reply
e.preventDefault();
scope.state.writer.write(scope.state.mailList.selected);
scope.$apply();
} else if (e.keyCode === 27 && scope.state.lightbox !== undefined) {
// escape -> close current lightbox
e.preventDefault();
scope.state.lightbox = undefined;
scope.$apply();
} else if (e.keyCode === 27 && scope.state.nav.open) {
// escape -> close nav view
e.preventDefault();
scope.state.nav.toggle(false);
scope.$apply();
}
});
};
// select inbox if not yet selected
if (!$scope.state.nav.currentFolder) {
$scope.openFolder($scope.account.folders[0]);
$scope.$apply();
}
});
return NavigationCtrl;
});
//
// helper functions
//
function initializeFolders() {
// create dummy folder in dev environment only
if ($routeParams.dev) {
createDummyFolders();
return;
}
// get pointer to account/folder/message tree on root scope
$scope.$root.account = emailDao._account;
// set notificatio handler for sent messages
outboxBo.onSent = sentNotification;
// start checking outbox periodically
outboxBo.startChecking($scope.onOutboxUpdate);
}
function sentNotification(email) {
notification.create({
title: 'Message sent',
message: email.subject,
timeout: 2000
}, function() {});
}
// attach dummy folders for development
function createDummyFolders() {
$scope.$root.account = {};
$scope.account.folders = [{
type: 'Inbox',
count: 2,
path: 'INBOX'
}, {
type: 'Sent',
count: 0,
path: 'SENT'
}, {
type: config.outboxMailboxType,
count: 0,
path: config.outboxMailboxPath
}, {
type: 'Drafts',
count: 0,
path: 'DRAFTS'
}, {
type: 'Trash',
count: 0,
path: 'TRASH'
}];
}
};
//
// Directives
//
var ngModule = angular.module('navigation', []);
ngModule.directive('keyShortcuts', function($timeout) {
return function(scope, elm) {
elm.bind('keydown', function(e) {
// global state is not yet set, ignore keybaord shortcuts
if (!scope.state) {
return;
}
var modifier = e.ctrlKey || e.metaKey;
if (modifier && e.keyCode === 78 && scope.state.lightbox !== 'write') {
// n -> new mail
e.preventDefault();
scope.state.writer.write();
scope.$apply();
} else if (modifier && e.keyCode === 70 && scope.state.lightbox !== 'write') {
// f -> find
e.preventDefault();
scope.state.mailList.searching = true;
$timeout(function() {
scope.state.mailList.searching = false;
}, 200);
scope.$apply();
} else if (modifier && e.keyCode === 82 && scope.state.lightbox !== 'write' && scope.state.mailList.selected) {
// r -> reply
e.preventDefault();
scope.state.writer.write(scope.state.mailList.selected);
scope.$apply();
} else if (e.keyCode === 27 && scope.state.lightbox !== undefined) {
// escape -> close current lightbox
e.preventDefault();
scope.state.lightbox = undefined;
scope.$apply();
} else if (e.keyCode === 27 && scope.state.nav.open) {
// escape -> close nav view
e.preventDefault();
scope.state.nav.toggle(false);
scope.$apply();
}
});
};
});
module.exports = NavigationCtrl;

View File

@ -1,47 +1,43 @@
define(function(require) {
'use strict';
'use strict';
var angular = require('angular');
//
// Controller
//
//
// Controller
//
var PopoverCtrl = function($scope) {
$scope.state.popover = {};
};
var PopoverCtrl = function($scope) {
$scope.state.popover = {};
//
// Directives
//
var ngModule = angular.module('popover', []);
ngModule.directive('popover', function() {
return function(scope, elm, attrs) {
var selector = attrs.popover;
var popover = angular.element(document.querySelector(selector));
elm.on('mouseover', function() {
// set popover position
var top = elm[0].offsetTop;
var left = elm[0].offsetLeft;
var width = elm[0].offsetWidth;
var height = elm[0].offsetHeight;
popover[0].style.transition = 'opacity 0.3s linear';
popover[0].style.top = (top + height / 2 - popover[0].offsetHeight / 2) + 'px';
popover[0].style.left = (left + width) + 'px';
popover[0].style.opacity = '1';
});
elm.on('mouseout', function() {
popover[0].style.transition = 'opacity 0.3s linear, top 0.3s step-end, left 0.3s step-end';
popover[0].style.opacity = '0';
popover[0].style.top = '-9999px';
popover[0].style.left = '-9999px';
});
};
});
//
// Directives
//
var ngModule = angular.module('popover', []);
ngModule.directive('popover', function() {
return function(scope, elm, attrs) {
var selector = attrs.popover;
var popover = angular.element(document.querySelector(selector));
elm.on('mouseover', function() {
// set popover position
var top = elm[0].offsetTop;
var left = elm[0].offsetLeft;
var width = elm[0].offsetWidth;
var height = elm[0].offsetHeight;
popover[0].style.transition = 'opacity 0.3s linear';
popover[0].style.top = (top + height / 2 - popover[0].offsetHeight / 2) + 'px';
popover[0].style.left = (left + width) + 'px';
popover[0].style.opacity = '1';
});
elm.on('mouseout', function() {
popover[0].style.transition = 'opacity 0.3s linear, top 0.3s step-end, left 0.3s step-end';
popover[0].style.opacity = '0';
popover[0].style.top = '-9999px';
popover[0].style.left = '-9999px';
});
};
});
return PopoverCtrl;
});
module.exports = PopoverCtrl;

View File

@ -1,204 +1,201 @@
define(function(require) {
'use strict';
'use strict';
var angular = require('angular'),
appController = require('js/app-controller'),
util = require('js/crypto/util'),
keychain, pgp;
var appController = require('../app-controller'),
util = require('crypto-lib').util,
keychain, pgp;
var PrivateKeyUploadCtrl = function($scope) {
keychain = appController._keychain;
pgp = keychain._pgp;
var PrivateKeyUploadCtrl = function($scope) {
keychain = appController._keychain;
pgp = keychain._pgp;
$scope.state.privateKeyUpload = {
toggle: function(to) {
// open lightbox
$scope.state.lightbox = (to) ? 'privatekey-upload' : undefined;
if (!to) {
return;
}
// show syncing status
$scope.step = 4;
// check if key is already synced
$scope.checkServerForKey(function(privateKeySynced) {
if (privateKeySynced) {
// close lightbox
$scope.state.lightbox = undefined;
// show message
$scope.onError({
title: 'Info',
message: 'Your PGP key has already been synced.'
});
return;
}
// show sync ui if key is not synced
$scope.displayUploadUi();
});
}
};
$scope.handlePaste = function(event) {
var evt = event;
if (evt.originalEvent) {
evt = evt.originalEvent;
}
var value = evt.clipboardData.getData('text/plain');
if (!value) {
$scope.state.privateKeyUpload = {
toggle: function(to) {
// open lightbox
$scope.state.lightbox = (to) ? 'privatekey-upload' : undefined;
if (!to) {
return;
}
value = value.replace(/-/g, '');
$scope.code0 = value.slice(0, 4);
$scope.code1 = value.slice(4, 8);
$scope.code2 = value.slice(8, 12);
$scope.code3 = value.slice(12, 16);
$scope.code4 = value.slice(16, 20);
$scope.code5 = value.slice(20, 24);
};
$scope.checkServerForKey = function(callback) {
var keyParams = pgp.getKeyParams();
keychain.hasPrivateKey({
userId: keyParams.userId,
keyId: keyParams._id
}, function(err, privateKeySynced) {
if (err) {
$scope.onError(err);
return;
}
// show syncing status
$scope.step = 4;
// check if key is already synced
$scope.checkServerForKey(function(privateKeySynced) {
if (privateKeySynced) {
callback(privateKeySynced);
// close lightbox
$scope.state.lightbox = undefined;
// show message
$scope.onError({
title: 'Info',
message: 'Your PGP key has already been synced.'
});
return;
}
callback();
// show sync ui if key is not synced
$scope.displayUploadUi();
});
};
}
};
$scope.displayUploadUi = function() {
// go to step 1
$scope.step = 1;
// generate new code for the user
$scope.code = util.randomString(24);
$scope.displayedCode = $scope.code.slice(0, 4) + '-' + $scope.code.slice(4, 8) + '-' + $scope.code.slice(8, 12) + '-' + $scope.code.slice(12, 16) + '-' + $scope.code.slice(16, 20) + '-' + $scope.code.slice(20, 24);
$scope.handlePaste = function(event) {
var evt = event;
if (evt.originalEvent) {
evt = evt.originalEvent;
}
// clear input fields of any previous artifacts
$scope.code0 = $scope.code1 = $scope.code2 = $scope.code3 = $scope.code4 = $scope.code5 = '';
};
var value = evt.clipboardData.getData('text/plain');
if (!value) {
return;
}
$scope.verifyCode = function() {
var inputCode = '' + $scope.code0 + $scope.code1 + $scope.code2 + $scope.code3 + $scope.code4 + $scope.code5;
value = value.replace(/-/g, '');
$scope.code0 = value.slice(0, 4);
$scope.code1 = value.slice(4, 8);
$scope.code2 = value.slice(8, 12);
$scope.code3 = value.slice(12, 16);
$scope.code4 = value.slice(16, 20);
$scope.code5 = value.slice(20, 24);
};
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.checkServerForKey = function(callback) {
var keyParams = pgp.getKeyParams();
keychain.hasPrivateKey({
userId: keyParams.userId,
keyId: keyParams._id
}, function(err, privateKeySynced) {
if (err) {
$scope.onError(err);
return false;
return;
}
return true;
};
if (privateKeySynced) {
callback(privateKeySynced);
return;
}
$scope.setDeviceName = function(callback) {
keychain.setDeviceName($scope.deviceName, callback);
};
callback();
});
};
$scope.encryptAndUploadKey = function(callback) {
var userId = appController._emailDao._account.emailAddress;
var code = $scope.code;
$scope.displayUploadUi = function() {
// go to step 1
$scope.step = 1;
// generate new code for the user
$scope.code = util.randomString(24);
$scope.displayedCode = $scope.code.slice(0, 4) + '-' + $scope.code.slice(4, 8) + '-' + $scope.code.slice(8, 12) + '-' + $scope.code.slice(12, 16) + '-' + $scope.code.slice(16, 20) + '-' + $scope.code.slice(20, 24);
// register device to keychain service
keychain.registerDevice({
userId: userId
}, function(err) {
// clear input fields of any previous artifacts
$scope.code0 = $scope.code1 = $scope.code2 = $scope.code3 = $scope.code4 = $scope.code5 = '';
};
$scope.verifyCode = function() {
var inputCode = '' + $scope.code0 + $scope.code1 + $scope.code2 + $scope.code3 + $scope.code4 + $scope.code5;
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);
return false;
}
return true;
};
$scope.setDeviceName = function(callback) {
keychain.setDeviceName($scope.deviceName, callback);
};
$scope.encryptAndUploadKey = function(callback) {
var userId = appController._emailDao._account.emailAddress;
var code = $scope.code;
// register device to keychain service
keychain.registerDevice({
userId: userId
}, function(err) {
if (err) {
$scope.onError(err);
return;
}
// encrypt private PGP key using code and upload
keychain.uploadPrivateKey({
userId: userId,
code: code
}, callback);
});
};
$scope.goBack = function() {
if ($scope.step > 1) {
$scope.step--;
}
};
$scope.goForward = function() {
if ($scope.step < 2) {
$scope.step++;
return;
}
if ($scope.step === 2 && $scope.verifyCode()) {
$scope.step++;
return;
}
if ($scope.step === 3) {
// set device name to local storage
$scope.setDeviceName(function(err) {
if (err) {
$scope.onError(err);
return;
}
// encrypt private PGP key using code and upload
keychain.uploadPrivateKey({
userId: userId,
code: code
}, callback);
});
};
$scope.goBack = function() {
if ($scope.step > 1) {
$scope.step--;
}
};
$scope.goForward = function() {
if ($scope.step < 2) {
// show spinner
$scope.step++;
return;
}
$scope.$apply();
if ($scope.step === 2 && $scope.verifyCode()) {
$scope.step++;
return;
}
if ($scope.step === 3) {
// set device name to local storage
$scope.setDeviceName(function(err) {
// init key sync
$scope.encryptAndUploadKey(function(err) {
if (err) {
$scope.onError(err);
return;
}
// show spinner
$scope.step++;
$scope.$apply();
// init key sync
$scope.encryptAndUploadKey(function(err) {
if (err) {
$scope.onError(err);
return;
}
// close sync dialog
$scope.state.privateKeyUpload.toggle(false);
// show success message
$scope.onError({
title: 'Success',
message: 'Whiteout Keychain setup successful!'
});
// close sync dialog
$scope.state.privateKeyUpload.toggle(false);
// show success message
$scope.onError({
title: 'Success',
message: 'Whiteout Keychain setup successful!'
});
});
}
};
});
}
};
//
// Directives
//
};
var ngModule = angular.module('privatekey-upload', []);
ngModule.directive('focusNext', function() {
return {
link: function(scope, element, attr) {
var maxLen = element[0].maxLength;
//
// Directives
//
scope.$watch(attr.ngModel, function(val) {
if (val && val.length === maxLen) {
var nextinput = element.next('input');
if (nextinput.length) {
nextinput[0].focus();
}
var ngModule = angular.module('privatekey-upload', []);
ngModule.directive('focusNext', function() {
return {
link: function(scope, element, attr) {
var maxLen = element[0].maxLength;
scope.$watch(attr.ngModel, function(val) {
if (val && val.length === maxLen) {
var nextinput = element.next('input');
if (nextinput.length) {
nextinput[0].focus();
}
});
}
};
});
}
});
}
};
});
return PrivateKeyUploadCtrl;
});
module.exports = PrivateKeyUploadCtrl;

View File

@ -1,218 +1,215 @@
(function() {
'use strict';
'use strict';
// set listener for event from main window
window.onmessage = function(e) {
var html = '';
// set listener for event from main window
window.onmessage = function(e) {
var html = '';
if (e.data.html) {
// display html mail body
html = '<div class="scale-body">' + e.data.html + '</div>';
} else if (e.data.text) {
// diplay text mail body by with colored conversation nodes
html = renderNodes(parseConversation(e.data.text));
}
if (e.data.html) {
// display html mail body
html = '<div class="scale-body">' + e.data.html + '</div>';
} else if (e.data.text) {
// diplay text mail body by with colored conversation nodes
html = renderNodes(parseConversation(e.data.text));
}
// sanitize HTML content: https://github.com/cure53/DOMPurify
html = window.DOMPurify.sanitize(html);
// make links open in a new window
html = html.replace(/<a /g, '<a target="_blank" ');
// sanitize HTML content: https://github.com/cure53/DOMPurify
html = window.DOMPurify.sanitize(html);
// make links open in a new window
html = html.replace(/<a /g, '<a target="_blank" ');
// remove sources where necessary
if (e.data.removeImages) {
html = html.replace(/(<img[^>]+\b)src=['"][^'">]+['"]/ig, function(match, prefix) {
return prefix;
// remove sources where necessary
if (e.data.removeImages) {
html = html.replace(/(<img[^>]+\b)src=['"][^'">]+['"]/ig, function(match, prefix) {
return prefix;
});
}
document.body.innerHTML = html;
scaleToFit();
};
window.addEventListener('resize', scaleToFit);
/**
* Parse email body and generate conversation nodes
* @param {Object} email The email object
* @return {Node} The root node of the conversion
*/
function parseConversation(textBody) {
var nodes;
function parseLines(body) {
var lines = [];
body.split('\n').forEach(parseLine);
function parseLine(line) {
var regex = /^>*/;
var result = regex.exec(line);
lines.push({
text: line.replace(regex, '').trim(),
level: (result && result.length > 0) ? result[0].length : 0
});
}
document.body.innerHTML = html;
return lines;
}
scaleToFit();
function buildTextNodes(lines) {
var i, j, root, currentLevel, currentNode, levelDelta;
root = new Node();
currentLevel = 0;
currentNode = root;
// iterate over text lines
for (i = 0; i < lines.length; i++) {
levelDelta = lines[i].level - currentLevel;
if (levelDelta === 0) {
// we are at the desired node ... no traversal required
} else if (levelDelta > 0) {
// traverse to child node(s)
for (j = 0; j < levelDelta; j++) {
var newChild = new Node(currentNode);
// create new child node
currentNode.children.push(newChild);
// go to last child node
currentNode = newChild;
// increase current level by one
currentLevel++;
}
} else {
// traverse to parent(s)
for (j = levelDelta; j < 0; j++) {
currentNode = currentNode.parent;
currentLevel--;
}
}
// add text to the current node
currentNode.addLine(lines[i].text);
}
return root;
}
function Node(parent) {
this.parent = parent;
this.children = [];
}
Node.prototype.addLine = function(lineText) {
var c, l;
c = this.children;
l = c.length;
// append text node to children if last child is not a text node
if (l < 1 || typeof c[l - 1] !== 'string') {
c[l] = '';
l = c.length;
}
// append line to last child (add newline between lines)
c[l - 1] += lineText + '\n';
};
window.addEventListener('resize', scaleToFit);
/**
* Parse email body and generate conversation nodes
* @param {Object} email The email object
* @return {Node} The root node of the conversion
*/
function parseConversation(textBody) {
var nodes;
function parseLines(body) {
var lines = [];
body.split('\n').forEach(parseLine);
function parseLine(line) {
var regex = /^>*/;
var result = regex.exec(line);
lines.push({
text: line.replace(regex, '').trim(),
level: (result && result.length > 0) ? result[0].length : 0
});
}
return lines;
}
function buildTextNodes(lines) {
var i, j, root, currentLevel, currentNode, levelDelta;
root = new Node();
currentLevel = 0;
currentNode = root;
// iterate over text lines
for (i = 0; i < lines.length; i++) {
levelDelta = lines[i].level - currentLevel;
if (levelDelta === 0) {
// we are at the desired node ... no traversal required
} else if (levelDelta > 0) {
// traverse to child node(s)
for (j = 0; j < levelDelta; j++) {
var newChild = new Node(currentNode);
// create new child node
currentNode.children.push(newChild);
// go to last child node
currentNode = newChild;
// increase current level by one
currentLevel++;
}
} else {
// traverse to parent(s)
for (j = levelDelta; j < 0; j++) {
currentNode = currentNode.parent;
currentLevel--;
}
}
// add text to the current node
currentNode.addLine(lines[i].text);
}
return root;
}
function Node(parent) {
this.parent = parent;
this.children = [];
}
Node.prototype.addLine = function(lineText) {
var c, l;
c = this.children;
l = c.length;
// append text node to children if last child is not a text node
if (l < 1 || typeof c[l - 1] !== 'string') {
c[l] = '';
l = c.length;
}
// append line to last child (add newline between lines)
c[l - 1] += lineText + '\n';
};
function removeParentReference(node) {
if (!node.children) {
// this is a text leaf ... terminate recursion
return;
}
// remove parent node to prevent infinite loop in JSON stringify
delete node.parent;
for (var i = 0; i < node.children.length; i++) {
if (typeof node.children[i] === 'string') {
// remove trailing newline in string
node.children[i] = node.children[i].replace(/\n$/, '');
} else {
// I used recursion ...
removeParentReference(node.children[i]);
}
}
}
nodes = buildTextNodes(parseLines(textBody.replace(/ >/g, '>')));
removeParentReference(nodes);
return nodes;
}
/**
* Render the conversation nodes as markup. This is not injected directly into the DOM, but rather send to a sandboxed iframe to be rendered
* @param {Node} root The conversation root node
* @return {Strin} The conversation as markup
*/
function renderNodes(root) {
var body = '';
function render(node) {
var i, html = '';
if (!node.children) {
// this is a text leaf
var lines = node.split('\n');
for (i = 0; i < lines.length; i++) {
// replace all urls with anchors
lines[i] = lines[i].replace(/(https?:\/\/[^\s]+)/g, createArchor);
// wrap line into an element for easier styling
html += '<div class="line';
if (isLineEmpty(lines[i])) {
html += ' empty-line';
}
html += '"><span>' + lines[i] + '</span><br></div>';
}
return html;
}
for (i = 0; i < node.children.length; i++) {
html += '<div class="prev-message">' + render(node.children[i]) + '</div>';
}
return html;
}
function createArchor(url) {
return '<a href="' + url + '">' + url + '</a>';
}
function isLineEmpty(line) {
return line.replace(/>/g, '').trim().length === 0;
}
for (var j = 0; j < root.children.length; j++) {
// start by rendering the root nodes children
body += render(root.children[j]);
}
return '<div class="view-read-body">' + body + '</div>';
}
/**
* Transform scale content to fit iframe width
*/
function scaleToFit() {
var view = document.getElementsByClassName('scale-body').item(0);
if(!view) {
function removeParentReference(node) {
if (!node.children) {
// this is a text leaf ... terminate recursion
return;
}
var parentWidth = view.parentNode.offsetWidth;
var w = view.offsetWidth;
var scale = '';
// remove parent node to prevent infinite loop in JSON stringify
delete node.parent;
if(w > parentWidth) {
scale = parentWidth / w;
scale = 'scale(' + scale + ',' + scale + ')';
for (var i = 0; i < node.children.length; i++) {
if (typeof node.children[i] === 'string') {
// remove trailing newline in string
node.children[i] = node.children[i].replace(/\n$/, '');
} else {
// I used recursion ...
removeParentReference(node.children[i]);
}
}
view.style['-webkit-transform-origin'] = '0 0';
view.style.transformOrigin = '0 0';
view.style['-webkit-transform'] = scale;
view.style.transform = scale;
}
})();
nodes = buildTextNodes(parseLines(textBody.replace(/ >/g, '>')));
removeParentReference(nodes);
return nodes;
}
/**
* Render the conversation nodes as markup. This is not injected directly into the DOM, but rather send to a sandboxed iframe to be rendered
* @param {Node} root The conversation root node
* @return {Strin} The conversation as markup
*/
function renderNodes(root) {
var body = '';
function render(node) {
var i, html = '';
if (!node.children) {
// this is a text leaf
var lines = node.split('\n');
for (i = 0; i < lines.length; i++) {
// replace all urls with anchors
lines[i] = lines[i].replace(/(https?:\/\/[^\s]+)/g, createArchor);
// wrap line into an element for easier styling
html += '<div class="line';
if (isLineEmpty(lines[i])) {
html += ' empty-line';
}
html += '"><span>' + lines[i] + '</span><br></div>';
}
return html;
}
for (i = 0; i < node.children.length; i++) {
html += '<div class="prev-message">' + render(node.children[i]) + '</div>';
}
return html;
}
function createArchor(url) {
return '<a href="' + url + '">' + url + '</a>';
}
function isLineEmpty(line) {
return line.replace(/>/g, '').trim().length === 0;
}
for (var j = 0; j < root.children.length; j++) {
// start by rendering the root nodes children
body += render(root.children[j]);
}
return '<div class="view-read-body">' + body + '</div>';
}
/**
* Transform scale content to fit iframe width
*/
function scaleToFit() {
var view = document.getElementsByClassName('scale-body').item(0);
if (!view) {
return;
}
var parentWidth = view.parentNode.offsetWidth;
var w = view.offsetWidth;
var scale = '';
if (w > parentWidth) {
scale = parentWidth / w;
scale = 'scale(' + scale + ',' + scale + ')';
}
view.style['-webkit-transform-origin'] = '0 0';
view.style.transformOrigin = '0 0';
view.style['-webkit-transform'] = scale;
view.style.transform = scale;
}

View File

@ -1,286 +1,283 @@
define(function(require) {
'use strict';
'use strict';
var appController = require('js/app-controller'),
download = require('js/util/download'),
angular = require('angular'),
str = require('js/app-config').string,
emailDao, invitationDao, outbox, pgp, keychain;
var appController = require('../app-controller'),
download = require('../util/download'),
str = require('../app-config').string,
emailDao, invitationDao, outbox, pgp, keychain;
//
// Controller
//
//
// Controller
//
var ReadCtrl = function($scope) {
var ReadCtrl = function($scope) {
emailDao = appController._emailDao;
invitationDao = appController._invitationDao;
outbox = appController._outboxBo;
pgp = appController._pgp;
keychain = appController._keychain;
emailDao = appController._emailDao;
invitationDao = appController._invitationDao;
outbox = appController._outboxBo;
pgp = appController._pgp;
keychain = appController._keychain;
// set default value so that the popover height is correct on init
$scope.keyId = 'No key found.';
// set default value so that the popover height is correct on init
$scope.keyId = 'No key found.';
$scope.state.read = {
open: false,
toggle: function(to) {
this.open = to;
}
};
$scope.getKeyId = function(address) {
$scope.keyId = 'Searching...';
keychain.getReceiverPublicKey(address, function(err, pubkey) {
if (err) {
$scope.onError(err);
return;
}
if (!pubkey) {
$scope.keyId = 'User has no key. Click to invite.';
$scope.$apply();
return;
}
var fpr = pgp.getFingerprint(pubkey.publicKey);
var formatted = fpr.slice(32);
$scope.keyId = 'PGP key: ' + formatted;
$scope.$apply();
});
};
$scope.$watch('state.mailList.selected', function(mail) {
if (!mail) {
return;
}
// display sender security status
mail.from.forEach(checkPublicKey);
// display recipient security status
mail.to.forEach(checkPublicKey);
// display recipient security status
Array.isArray(mail.cc) && mail.cc.forEach(checkPublicKey);
});
function checkPublicKey(user) {
user.secure = undefined;
if (!keychain) {
return;
}
keychain.getReceiverPublicKey(user.address, function(err, pubkey) {
if (err) {
$scope.onError(err);
return;
}
if (pubkey && pubkey.publicKey) {
user.secure = true;
} else {
user.secure = false;
}
$scope.$apply();
});
$scope.state.read = {
open: false,
toggle: function(to) {
this.open = to;
}
$scope.download = function(attachment) {
// download file to disk if content is available
if (attachment.content) {
download.createDownload({
content: attachment.content,
filename: attachment.filename,
contentType: attachment.mimeType
});
return;
}
var folder = $scope.state.nav.currentFolder;
var email = $scope.state.mailList.selected;
emailDao.getAttachment({
folder: folder,
uid: email.uid,
attachment: attachment
}, $scope.onError);
};
$scope.invite = function(user) {
// only invite non-pgp users
if (user.secure) {
return;
}
$scope.keyId = 'Sending invitation...';
var sender = emailDao._account.emailAddress,
recipient = user.address;
invitationDao.invite({
recipient: recipient,
sender: sender
}, function(err) {
if (err) {
$scope.onError(err);
return;
}
var invitationMail = {
from: [{
address: sender
}],
to: [{
address: recipient
}],
cc: [],
bcc: [],
subject: str.invitationSubject,
body: str.invitationMessage
};
// send invitation mail
outbox.put(invitationMail, $scope.onError);
});
};
};
//
// Directives
//
var ngModule = angular.module('read', []);
ngModule.directive('replySelection', function() {
return function(scope, elm) {
var popover, visible;
popover = angular.element(document.querySelector('.reply-selection'));
visible = false;
elm.on('touchstart click', appear);
elm.parent().parent().on('touchstart click', disappear);
popover.on('touchstart click', disappear);
function appear(e) {
e.preventDefault();
e.stopPropagation();
visible = true;
// set popover position
var top = elm[0].offsetTop;
var left = elm[0].offsetLeft;
var width = elm[0].offsetWidth;
var height = elm[0].offsetHeight;
popover[0].style.transition = 'opacity 0.1s linear';
popover[0].style.top = (top + height) + 'px';
popover[0].style.left = (left + width / 2 - popover[0].offsetWidth / 2) + 'px';
popover[0].style.opacity = '1';
$scope.getKeyId = function(address) {
$scope.keyId = 'Searching...';
keychain.getReceiverPublicKey(address, function(err, pubkey) {
if (err) {
$scope.onError(err);
return;
}
function disappear() {
if (!visible) {
return;
}
popover[0].style.transition = 'opacity 0.25s linear, top 0.25s step-end, left 0.25s step-end';
popover[0].style.opacity = '0';
popover[0].style.top = '-9999px';
popover[0].style.left = '-9999px';
visible = false;
if (!pubkey) {
$scope.keyId = 'User has no key. Click to invite.';
$scope.$apply();
return;
}
};
var fpr = pgp.getFingerprint(pubkey.publicKey);
var formatted = fpr.slice(32);
$scope.keyId = 'PGP key: ' + formatted;
$scope.$apply();
});
};
$scope.$watch('state.mailList.selected', function(mail) {
if (!mail) {
return;
}
// display sender security status
mail.from.forEach(checkPublicKey);
// display recipient security status
mail.to.forEach(checkPublicKey);
// display recipient security status
Array.isArray(mail.cc) && mail.cc.forEach(checkPublicKey);
});
ngModule.directive('frameLoad', function($timeout, $window) {
return function(scope, elm) {
var iframe = elm[0];
function checkPublicKey(user) {
user.secure = undefined;
scope.$watch('state.read.open', function(open) {
if (open) {
// trigger rendering of iframe
// otherwise scale to fit would not compute correct dimensions on mobile
displayText(scope.state.mailList.selected ? scope.state.mailList.selected.body : undefined);
displayHtml(scope.state.mailList.selected ? scope.state.mailList.selected.html : undefined);
}
if (!keychain) {
return;
}
keychain.getReceiverPublicKey(user.address, function(err, pubkey) {
if (err) {
$scope.onError(err);
return;
}
if (pubkey && pubkey.publicKey) {
user.secure = true;
} else {
user.secure = false;
}
$scope.$apply();
});
}
$scope.download = function(attachment) {
// download file to disk if content is available
if (attachment.content) {
download.createDownload({
content: attachment.content,
filename: attachment.filename,
contentType: attachment.mimeType
});
return;
}
$window.addEventListener('resize', scaleToFit);
var folder = $scope.state.nav.currentFolder;
var email = $scope.state.mailList.selected;
emailDao.getAttachment({
folder: folder,
uid: email.uid,
attachment: attachment
}, $scope.onError);
};
iframe.onload = function() {
// set listeners
scope.$watch('state.mailList.selected.body', displayText);
scope.$watch('state.mailList.selected.html', displayHtml);
// display initial message body
scope.$apply();
$scope.invite = function(user) {
// only invite non-pgp users
if (user.secure) {
return;
}
$scope.keyId = 'Sending invitation...';
var sender = emailDao._account.emailAddress,
recipient = user.address;
invitationDao.invite({
recipient: recipient,
sender: sender
}, function(err) {
if (err) {
$scope.onError(err);
return;
}
var invitationMail = {
from: [{
address: sender
}],
to: [{
address: recipient
}],
cc: [],
bcc: [],
subject: str.invitationSubject,
body: str.invitationMessage
};
function displayText(body) {
var mail = scope.state.mailList.selected;
if ((mail && mail.html) || (mail && mail.encrypted && !mail.decrypted)) {
return;
}
// send invitation mail
outbox.put(invitationMail, $scope.onError);
});
};
};
// send text body for rendering in iframe
iframe.contentWindow.postMessage({
text: body
}, '*');
//
// Directives
//
$timeout(scaleToFit, 0);
var ngModule = angular.module('read', []);
ngModule.directive('replySelection', function() {
return function(scope, elm) {
var popover, visible;
popover = angular.element(document.querySelector('.reply-selection'));
visible = false;
elm.on('touchstart click', appear);
elm.parent().parent().on('touchstart click', disappear);
popover.on('touchstart click', disappear);
function appear(e) {
e.preventDefault();
e.stopPropagation();
visible = true;
// set popover position
var top = elm[0].offsetTop;
var left = elm[0].offsetLeft;
var width = elm[0].offsetWidth;
var height = elm[0].offsetHeight;
popover[0].style.transition = 'opacity 0.1s linear';
popover[0].style.top = (top + height) + 'px';
popover[0].style.left = (left + width / 2 - popover[0].offsetWidth / 2) + 'px';
popover[0].style.opacity = '1';
}
function disappear() {
if (!visible) {
return;
}
function displayHtml(html) {
if (!html) {
return;
}
popover[0].style.transition = 'opacity 0.25s linear, top 0.25s step-end, left 0.25s step-end';
popover[0].style.opacity = '0';
popover[0].style.top = '-9999px';
popover[0].style.left = '-9999px';
visible = false;
}
};
});
// if there are image tags in the html?
var hasImages = /<img[^>]+\bsrc=['"][^'">]+['"]/ig.test(html);
scope.showImageButton = hasImages;
ngModule.directive('frameLoad', function($timeout, $window) {
return function(scope, elm) {
var iframe = elm[0];
iframe.contentWindow.postMessage({
html: html,
removeImages: hasImages // avoids doing unnecessary work on the html
}, '*');
// only add a scope function to reload the html if there are images
if (hasImages) {
// reload WITH images
scope.displayImages = function() {
scope.showImageButton = false;
iframe.contentWindow.postMessage({
html: html,
removeImages: false
}, '*');
};
}
$timeout(scaleToFit, 0);
scope.$watch('state.read.open', function(open) {
if (open) {
// trigger rendering of iframe
// otherwise scale to fit would not compute correct dimensions on mobile
displayText(scope.state.mailList.selected ? scope.state.mailList.selected.body : undefined);
displayHtml(scope.state.mailList.selected ? scope.state.mailList.selected.html : undefined);
}
});
// transform scale iframe (necessary on iOS) to fit container width
function scaleToFit() {
var parentWidth = elm.parent().width();
var w = elm.width();
var scale = '';
$window.addEventListener('resize', scaleToFit);
if (w > parentWidth) {
scale = parentWidth / w;
scale = 'scale(' + scale + ',' + scale + ')';
}
elm.css({
'-webkit-transform-origin': '0 0',
'transform-origin': '0 0',
'-webkit-transform': scale,
'transform': scale
});
}
iframe.onload = function() {
// set listeners
scope.$watch('state.mailList.selected.body', displayText);
scope.$watch('state.mailList.selected.html', displayHtml);
// display initial message body
scope.$apply();
};
});
return ReadCtrl;
});
function displayText(body) {
var mail = scope.state.mailList.selected;
if ((mail && mail.html) || (mail && mail.encrypted && !mail.decrypted)) {
return;
}
// send text body for rendering in iframe
iframe.contentWindow.postMessage({
text: body
}, '*');
$timeout(scaleToFit, 0);
}
function displayHtml(html) {
if (!html) {
return;
}
// if there are image tags in the html?
var hasImages = /<img[^>]+\bsrc=['"][^'">]+['"]/ig.test(html);
scope.showImageButton = hasImages;
iframe.contentWindow.postMessage({
html: html,
removeImages: hasImages // avoids doing unnecessary work on the html
}, '*');
// only add a scope function to reload the html if there are images
if (hasImages) {
// reload WITH images
scope.displayImages = function() {
scope.showImageButton = false;
iframe.contentWindow.postMessage({
html: html,
removeImages: false
}, '*');
};
}
$timeout(scaleToFit, 0);
}
// transform scale iframe (necessary on iOS) to fit container width
function scaleToFit() {
var parentWidth = elm.parent().width();
var w = elm.width();
var scale = '';
if (w > parentWidth) {
scale = parentWidth / w;
scale = 'scale(' + scale + ',' + scale + ')';
}
elm.css({
'-webkit-transform-origin': '0 0',
'transform-origin': '0 0',
'-webkit-transform': scale,
'transform': scale
});
}
};
});
module.exports = ReadCtrl;

View File

@ -1,138 +1,136 @@
define(function(require) {
'use strict';
'use strict';
var appController = require('js/app-controller'),
pgp, keychain;
var appController = require('../app-controller'),
pgp, keychain;
//
// Controller
//
//
// Controller
//
var SetPassphraseCtrl = function($scope) {
keychain = appController._keychain;
pgp = appController._pgp;
var SetPassphraseCtrl = function($scope) {
keychain = appController._keychain;
pgp = appController._pgp;
$scope.state.setPassphrase = {
toggle: function(to) {
$scope.state.lightbox = (to) ? 'set-passphrase' : undefined;
$scope.state.setPassphrase = {
toggle: function(to) {
$scope.state.lightbox = (to) ? 'set-passphrase' : undefined;
$scope.newPassphrase = undefined;
$scope.oldPassphrase = undefined;
$scope.confirmation = undefined;
$scope.passphraseMsg = undefined;
}
};
//
// scope variables
//
//
// scope functions
//
/*
* Taken from jQuery validate.password plug-in 1.0
* http://bassistance.de/jquery-plugins/jquery-plugin-validate.password/
*
* Copyright (c) 2009 Jörn Zaefferer
*
* Licensed under the MIT
* http://www.opensource.org/licenses/mit-license.php
*/
$scope.checkPassphraseQuality = function() {
var passphrase = $scope.newPassphrase;
$scope.passphraseRating = 0;
var LOWER = /[a-z]/,
UPPER = /[A-Z]/,
DIGIT = /[0-9]/,
DIGITS = /[0-9].*[0-9]/,
SPECIAL = /[^a-zA-Z0-9]/,
SAME = /^(.)\1+$/;
function uncapitalize(str) {
return str.substring(0, 1).toLowerCase() + str.substring(1);
}
if (!passphrase) {
// no rating for empty passphrase
$scope.passphraseMsg = '';
return;
}
if (passphrase.length < 8 || SAME.test(passphrase)) {
$scope.passphraseMsg = 'Very weak';
return;
}
var lower = LOWER.test(passphrase),
upper = UPPER.test(uncapitalize(passphrase)),
digit = DIGIT.test(passphrase),
digits = DIGITS.test(passphrase),
special = SPECIAL.test(passphrase);
if (lower && upper && digit || lower && digits || upper && digits || special) {
$scope.passphraseMsg = 'Strong';
$scope.passphraseRating = 3;
} else if (lower && upper || lower && digit || upper && digit) {
$scope.passphraseMsg = 'Good';
$scope.passphraseRating = 2;
} else {
$scope.passphraseMsg = 'Weak';
$scope.passphraseRating = 1;
}
};
$scope.setPassphrase = function() {
var keyId = pgp.getKeyParams()._id;
keychain.lookupPrivateKey(keyId, function(err, savedKey) {
if (err) {
$scope.onError(err);
return;
}
pgp.changePassphrase({
privateKeyArmored: savedKey.encryptedKey,
oldPassphrase: $scope.oldPassphrase,
newPassphrase: $scope.newPassphrase
}, onPassphraseChanged);
});
};
function onPassphraseChanged(err, newPrivateKeyArmored) {
if (err) {
err.showBugReporter = false;
$scope.onError(err);
return;
}
// persist new armored key
var keyParams = pgp.getKeyParams(newPrivateKeyArmored);
var privateKey = {
_id: keyParams._id,
userId: keyParams.userId,
userIds: keyParams.userIds,
encryptedKey: newPrivateKeyArmored
};
keychain.saveLocalPrivateKey(privateKey, onKeyPersisted);
}
function onKeyPersisted(err) {
if (err) {
$scope.onError(err);
return;
}
$scope.state.setPassphrase.toggle(false);
$scope.$apply();
$scope.onError({
title: 'Success',
message: 'Passphrase change complete.'
});
$scope.newPassphrase = undefined;
$scope.oldPassphrase = undefined;
$scope.confirmation = undefined;
$scope.passphraseMsg = undefined;
}
};
return SetPassphraseCtrl;
});
//
// scope variables
//
//
// scope functions
//
/*
* Taken from jQuery validate.password plug-in 1.0
* http://bassistance.de/jquery-plugins/jquery-plugin-validate.password/
*
* Copyright (c) 2009 Jörn Zaefferer
*
* Licensed under the MIT
* http://www.opensource.org/licenses/mit-license.php
*/
$scope.checkPassphraseQuality = function() {
var passphrase = $scope.newPassphrase;
$scope.passphraseRating = 0;
var LOWER = /[a-z]/,
UPPER = /[A-Z]/,
DIGIT = /[0-9]/,
DIGITS = /[0-9].*[0-9]/,
SPECIAL = /[^a-zA-Z0-9]/,
SAME = /^(.)\1+$/;
function uncapitalize(str) {
return str.substring(0, 1).toLowerCase() + str.substring(1);
}
if (!passphrase) {
// no rating for empty passphrase
$scope.passphraseMsg = '';
return;
}
if (passphrase.length < 8 || SAME.test(passphrase)) {
$scope.passphraseMsg = 'Very weak';
return;
}
var lower = LOWER.test(passphrase),
upper = UPPER.test(uncapitalize(passphrase)),
digit = DIGIT.test(passphrase),
digits = DIGITS.test(passphrase),
special = SPECIAL.test(passphrase);
if (lower && upper && digit || lower && digits || upper && digits || special) {
$scope.passphraseMsg = 'Strong';
$scope.passphraseRating = 3;
} else if (lower && upper || lower && digit || upper && digit) {
$scope.passphraseMsg = 'Good';
$scope.passphraseRating = 2;
} else {
$scope.passphraseMsg = 'Weak';
$scope.passphraseRating = 1;
}
};
$scope.setPassphrase = function() {
var keyId = pgp.getKeyParams()._id;
keychain.lookupPrivateKey(keyId, function(err, savedKey) {
if (err) {
$scope.onError(err);
return;
}
pgp.changePassphrase({
privateKeyArmored: savedKey.encryptedKey,
oldPassphrase: $scope.oldPassphrase,
newPassphrase: $scope.newPassphrase
}, onPassphraseChanged);
});
};
function onPassphraseChanged(err, newPrivateKeyArmored) {
if (err) {
err.showBugReporter = false;
$scope.onError(err);
return;
}
// persist new armored key
var keyParams = pgp.getKeyParams(newPrivateKeyArmored);
var privateKey = {
_id: keyParams._id,
userId: keyParams.userId,
userIds: keyParams.userIds,
encryptedKey: newPrivateKeyArmored
};
keychain.saveLocalPrivateKey(privateKey, onKeyPersisted);
}
function onKeyPersisted(err) {
if (err) {
$scope.onError(err);
return;
}
$scope.state.setPassphrase.toggle(false);
$scope.$apply();
$scope.onError({
title: 'Success',
message: 'Passphrase change complete.'
});
}
};
module.exports = SetPassphraseCtrl;

File diff suppressed because it is too large Load Diff

View File

@ -2,122 +2,119 @@
* High level crypto api that invokes native crypto (if available) and
* gracefully degrades to JS crypto (if unavailable)
*/
define(function(require) {
'use strict';
var aes = require('js/crypto/aes-gcm'),
pbkdf2 = require('js/crypto/pbkdf2'),
config = require('js/app-config').config,
axe = require('axe');
'use strict';
var PBKDF2_WORKER = '/crypto/pbkdf2-worker.js';
var aes = require('crypto-lib').aes,
pbkdf2 = require('./pbkdf2'),
config = require('../app-config').config,
axe = require('axe-logger');
var Crypto = function() {};
var Crypto = function() {};
/**
* Encrypt plaintext using AES-GCM.
* @param {String} plaintext The input string in UTF-16
* @param {String} key The base64 encoded key
* @param {String} iv The base64 encoded IV
* @param {Function} callback(error, ciphertext)
* @return {String} The base64 encoded ciphertext
*/
Crypto.prototype.encrypt = function(plaintext, key, iv, callback) {
var ct;
/**
* Encrypt plaintext using AES-GCM.
* @param {String} plaintext The input string in UTF-16
* @param {String} key The base64 encoded key
* @param {String} iv The base64 encoded IV
* @param {Function} callback(error, ciphertext)
* @return {String} The base64 encoded ciphertext
*/
Crypto.prototype.encrypt = function(plaintext, key, iv, callback) {
var ct;
try {
ct = aes.encrypt(plaintext, key, iv);
} catch (err) {
callback(err);
return;
try {
ct = aes.encrypt(plaintext, key, iv);
} catch (err) {
callback(err);
return;
}
callback(null, ct);
};
/**
* Decrypt ciphertext suing AES-GCM
* @param {String} ciphertext The base64 encoded ciphertext
* @param {String} key The base64 encoded key
* @param {String} iv The base64 encoded IV
* @param {Function} callback(error, plaintext)
* @return {String} The decrypted plaintext in UTF-16
*/
Crypto.prototype.decrypt = function(ciphertext, key, iv, callback) {
var pt;
try {
pt = aes.decrypt(ciphertext, key, iv);
} catch (err) {
callback(err);
return;
}
callback(null, pt);
};
/**
* Do PBKDF2 key derivation in a WebWorker thread
*/
Crypto.prototype.deriveKey = function(password, salt, keySize, callback) {
startWorker({
script: config.workerPath + '/pbkdf2-worker.min.js',
args: {
password: password,
salt: salt,
keySize: keySize
},
callback: callback,
noWorker: function() {
return pbkdf2.getKey(password, salt, keySize);
}
});
};
callback(null, ct);
};
//
// helper functions
//
/**
* Decrypt ciphertext suing AES-GCM
* @param {String} ciphertext The base64 encoded ciphertext
* @param {String} key The base64 encoded key
* @param {String} iv The base64 encoded IV
* @param {Function} callback(error, plaintext)
* @return {String} The decrypted plaintext in UTF-16
*/
Crypto.prototype.decrypt = function(ciphertext, key, iv, callback) {
var pt;
try {
pt = aes.decrypt(ciphertext, key, iv);
} catch (err) {
callback(err);
return;
}
callback(null, pt);
};
/**
* Do PBKDF2 key derivation in a WebWorker thread
*/
Crypto.prototype.deriveKey = function(password, salt, keySize, callback) {
startWorker({
script: PBKDF2_WORKER,
args: {
password: password,
salt: salt,
keySize: keySize
},
callback: callback,
noWorker: function() {
return pbkdf2.getKey(password, salt, keySize);
}
});
};
//
// helper functions
//
function startWorker(options) {
// check for WebWorker support
if (window.Worker) {
// init webworker thread
var worker = new Worker(config.workerPath + options.script);
worker.onmessage = function(e) {
if (e.data.err) {
options.callback(e.data.err);
return;
}
// return result from the worker
options.callback(null, e.data);
};
worker.onerror = function(e) {
// show error message in logger
axe.error('Error handling web worker: Line ' + e.lineno + ' in ' + e.filename + ': ' + e.message);
// return error
options.callback({
errMsg: (e.message) ? e.message : e
});
function startWorker(options) {
// check for WebWorker support
if (window.Worker) {
// init webworker thread
var worker = new Worker(options.script);
worker.onmessage = function(e) {
if (e.data.err) {
options.callback(e.data.err);
return;
};
// send data to the worker
worker.postMessage(options.args);
return;
}
// no WebWorker support... do synchronous call
var result;
try {
result = options.noWorker();
} catch (e) {
}
// return result from the worker
options.callback(null, e.data);
};
worker.onerror = function(e) {
// show error message in logger
axe.error('Error handling web worker: Line ' + e.lineno + ' in ' + e.filename + ': ' + e.message);
// return error
options.callback({
errMsg: (e.message) ? e.message : e
});
return;
}
options.callback(null, result);
};
// send data to the worker
worker.postMessage(options.args);
return;
}
return Crypto;
});
// no WebWorker support... do synchronous call
var result;
try {
result = options.noWorker();
} catch (e) {
// return error
options.callback({
errMsg: (e.message) ? e.message : e
});
return;
}
options.callback(null, result);
}
module.exports = Crypto;

View File

@ -1,38 +1,25 @@
(function() {
'use strict';
'use strict';
// import web worker dependencies
importScripts('../../lib/require.js');
importScripts('forge.min.js');
/**
* In the web worker thread context, 'this' and 'self' can be used as a global
* variable namespace similar to the 'window' object in the main thread
*/
self.onmessage = function(e) {
// fetch dependencies via require.js
require(['../../require-config'], function() {
require.config({
baseUrl: '../../lib'
});
var pbkdf2 = require('./pbkdf2');
require(['js/crypto/pbkdf2'], function(pbkdf2) {
/**
* In the web worker thread context, 'this' and 'self' can be used as a global
* variable namespace similar to the 'window' object in the main thread
*/
self.onmessage = function(e) {
var i = e.data,
key = null;
var i = e.data,
key = null;
if (i.password && i.salt && i.keySize) {
// start deriving key
key = pbkdf2.getKey(i.password, i.salt, i.keySize);
if (i.password && i.salt && i.keySize) {
// start deriving key
key = pbkdf2.getKey(i.password, i.salt, i.keySize);
} else {
throw 'Not all arguments for web worker crypto are defined!';
}
} else {
throw 'Not all arguments for web worker crypto are defined!';
}
// pass output back to main thread
self.postMessage(key);
});
});
};
}());
// pass output back to main thread
self.postMessage(key);
};

View File

@ -1,25 +1,24 @@
/**
* A Wrapper for Forge's PBKDF2 function
*/
define(['forge'], function(forge) {
'use strict';
var self = {};
'use strict';
/**
* PBKDF2-HMAC-SHA256 key derivation with a random salt and 10000 iterations
* @param {String} password The password in UTF8
* @param {String} salt The base64 encoded salt
* @param {String} keySize The key size in bits
* @return {String} The base64 encoded key
*/
self.getKey = function(password, salt, keySize) {
var saltUtf8 = forge.util.decode64(salt);
var md = forge.md.sha256.create();
var key = forge.pkcs5.pbkdf2(password, saltUtf8, 10000, keySize / 8, md);
var pbkdf2 = {};
return forge.util.encode64(key);
};
/**
* PBKDF2-HMAC-SHA256 key derivation with a random salt and 10000 iterations
* @param {String} password The password in UTF8
* @param {String} salt The base64 encoded salt
* @param {String} keySize The key size in bits
* @return {String} The base64 encoded key
*/
pbkdf2.getKey = function(password, salt, keySize) {
var saltUtf8 = forge.util.decode64(salt);
var md = forge.md.sha256.create();
var key = forge.pkcs5.pbkdf2(password, saltUtf8, 10000, keySize / 8, md);
return self;
});
return forge.util.encode64(key);
};
module.exports = pbkdf2;

View File

@ -1,452 +1,450 @@
/**
* High level crypto api that handles all calls to OpenPGP.js
*/
define(function(require) {
'use strict';
var openpgp = require('openpgp'),
util = require('openpgp').util,
config = require('js/app-config').config;
'use strict';
var PGP = function() {
openpgp.config.prefer_hash_algorithm = openpgp.enums.hash.sha256;
openpgp.initWorker(config.workerPath + '/../lib/openpgp/openpgp.worker.js');
};
var util = openpgp.util,
config = require('../app-config').config;
/**
* Generate a key pair for the user
*/
PGP.prototype.generateKeys = function(options, callback) {
var userId, passphrase;
var PGP = function() {
openpgp.config.prefer_hash_algorithm = openpgp.enums.hash.sha256;
openpgp.initWorker(config.workerPath + '/openpgp.worker.js');
};
if (!util.emailRegEx.test(options.emailAddress) || !options.keySize) {
callback(new Error('Crypto init failed. Not all options set!'));
return;
}
/**
* Generate a key pair for the user
*/
PGP.prototype.generateKeys = function(options, callback) {
var userId, passphrase;
// generate keypair
userId = 'Whiteout User <' + options.emailAddress + '>';
passphrase = (options.passphrase) ? options.passphrase : undefined;
openpgp.generateKeyPair({
keyType: 1, // (keytype 1=RSA)
numBits: options.keySize,
userId: userId,
passphrase: passphrase
}, onGenerated);
if (!util.emailRegEx.test(options.emailAddress) || !options.keySize) {
callback(new Error('Crypto init failed. Not all options set!'));
return;
}
function onGenerated(err, keys) {
if (err) {
callback(new Error('Keygeneration failed!'));
return;
}
// generate keypair
userId = 'Whiteout User <' + options.emailAddress + '>';
passphrase = (options.passphrase) ? options.passphrase : undefined;
openpgp.generateKeyPair({
keyType: 1, // (keytype 1=RSA)
numBits: options.keySize,
userId: userId,
passphrase: passphrase
}, onGenerated);
callback(null, {
keyId: keys.key.primaryKey.getKeyId().toHex().toUpperCase(),
privateKeyArmored: keys.privateKeyArmored,
publicKeyArmored: keys.publicKeyArmored
});
}
};
/**
* Show a user's fingerprint
*/
PGP.prototype.getFingerprint = function(keyArmored) {
function fingerprint(key) {
return key.primaryKey.getFingerprint().toUpperCase();
}
// process armored key input
if (keyArmored) {
return fingerprint(openpgp.key.readArmored(keyArmored).keys[0]);
}
if (!this._publicKey) {
throw new Error('No public key set for fingerprint generation!');
}
// get local fingerpring
return fingerprint(this._publicKey);
};
/**
* Show a user's key id.
*/
PGP.prototype.getKeyId = function(keyArmored) {
var key, pubKeyId, privKeyId;
// process armored key input
if (keyArmored) {
key = openpgp.key.readArmored(keyArmored).keys[0];
return key.primaryKey.getKeyId().toHex().toUpperCase();
}
// check already imported keys
if (!this._privateKey || !this._publicKey) {
throw new Error('Cannot read key IDs... keys not set!');
}
pubKeyId = this._publicKey.primaryKey.getKeyId().toHex().toUpperCase();
privKeyId = this._privateKey.primaryKey.getKeyId().toHex().toUpperCase();
if (!pubKeyId || !privKeyId || pubKeyId !== privKeyId) {
throw new Error('Key IDs do not match!');
}
return pubKeyId;
};
/**
* Read all relevant params of an armored key.
*/
PGP.prototype.getKeyParams = function(keyArmored) {
var key, packet, userIds;
// process armored key input
if (keyArmored) {
key = openpgp.key.readArmored(keyArmored).keys[0];
} else if (this._publicKey) {
key = this._publicKey;
} else {
throw new Error('Cannot read key params... keys not set!');
}
packet = key.primaryKey;
// read user names and email addresses
userIds = [];
key.getUserIds().forEach(function(userId) {
userIds.push({
name: userId.split('<')[0].trim(),
emailAddress: userId.split('<')[1].split('>')[0].trim()
});
});
return {
_id: packet.getKeyId().toHex().toUpperCase(),
userId: userIds[0].emailAddress, // the primary (first) email address of the key
userIds: userIds, // a dictonary of all the key's name/address pairs
fingerprint: packet.getFingerprint().toUpperCase(),
algorithm: packet.algorithm,
bitSize: packet.getBitSize(),
created: packet.created,
};
};
/**
* Extract a public key from a private key
* @param {String} privateKeyArmored The private PGP key block
* @return {String} The publick PGP key block
*/
PGP.prototype.extractPublicKey = function(privateKeyArmored) {
var privkey = openpgp.key.readArmored(privateKeyArmored).keys[0];
var pubkey = privkey.toPublic();
return pubkey.armor();
};
/**
* Import the user's key pair
*/
PGP.prototype.importKeys = function(options, callback) {
var pubKeyId, privKeyId, self = this;
// check options
if (!options.privateKeyArmored || !options.publicKeyArmored) {
callback(new Error('Importing keys failed. Not all options set!'));
return;
}
function resetKeys() {
self._publicKey = undefined;
self._privateKey = undefined;
}
// read armored keys
try {
this._publicKey = openpgp.key.readArmored(options.publicKeyArmored).keys[0];
this._privateKey = openpgp.key.readArmored(options.privateKeyArmored).keys[0];
} catch (e) {
resetKeys();
callback(new Error('Importing keys failed. Parsing error!'));
return;
}
// decrypt private key with passphrase
if (!this._privateKey.decrypt(options.passphrase)) {
resetKeys();
callback(new Error('Incorrect passphrase!'));
return;
}
// check if keys have the same id
pubKeyId = this._publicKey.primaryKey.getKeyId().toHex();
privKeyId = this._privateKey.primaryKey.getKeyId().toHex();
if (!pubKeyId || !privKeyId || pubKeyId !== privKeyId) {
resetKeys();
callback(new Error('Key IDs dont match!'));
return;
}
callback();
};
/**
* Export the user's key pair
*/
PGP.prototype.exportKeys = function(callback) {
if (!this._publicKey || !this._privateKey) {
callback(new Error('Could not export keys!'));
function onGenerated(err, keys) {
if (err) {
callback(new Error('Keygeneration failed!'));
return;
}
callback(null, {
keyId: this._publicKey.primaryKey.getKeyId().toHex().toUpperCase(),
privateKeyArmored: this._privateKey.armor(),
publicKeyArmored: this._publicKey.armor()
keyId: keys.key.primaryKey.getKeyId().toHex().toUpperCase(),
privateKeyArmored: keys.privateKeyArmored,
publicKeyArmored: keys.publicKeyArmored
});
};
}
};
/**
* Change the passphrase of an ascii armored private key.
*/
PGP.prototype.changePassphrase = function(options, callback) {
var privKey, packets, newPassphrase, newKeyArmored;
// set undefined instead of empty string as passphrase
newPassphrase = (options.newPassphrase) ? options.newPassphrase : undefined;
if (!options.privateKeyArmored) {
callback(new Error('Private key must be specified to change passphrase!'));
return;
}
if (options.oldPassphrase === newPassphrase ||
(!options.oldPassphrase && !newPassphrase)) {
callback(new Error('New and old passphrase are the same!'));
return;
}
// read armored key
try {
privKey = openpgp.key.readArmored(options.privateKeyArmored).keys[0];
} catch (e) {
callback(new Error('Importing key failed. Parsing error!'));
return;
}
// decrypt private key with passphrase
if (!privKey.decrypt(options.oldPassphrase)) {
callback(new Error('Old passphrase incorrect!'));
return;
}
// encrypt key with new passphrase
try {
packets = privKey.getAllKeyPackets();
for (var i = 0; i < packets.length; i++) {
packets[i].encrypt(newPassphrase);
}
newKeyArmored = privKey.armor();
} catch (e) {
callback(new Error('Setting new passphrase failed!'));
return;
}
// check if new passphrase really works
if (!privKey.decrypt(newPassphrase)) {
callback(new Error('Decrypting key with new passphrase failed!'));
return;
}
callback(null, newKeyArmored);
};
/**
* Encrypt and sign a pgp message for a list of receivers
*/
PGP.prototype.encrypt = function(plaintext, publicKeysArmored, callback) {
var publicKeys;
// check keys
if (!this._privateKey) {
callback(new Error('Error encrypting. Keys must be set!'));
return;
}
// parse armored public keys
try {
if (publicKeysArmored && publicKeysArmored.length) {
publicKeys = [];
publicKeysArmored.forEach(function(pubkeyArmored) {
publicKeys = publicKeys.concat(openpgp.key.readArmored(pubkeyArmored).keys);
});
}
} catch (err) {
callback(new Error('Error encrypting plaintext!'));
return;
}
if (publicKeys) {
// encrypt and sign the plaintext
openpgp.signAndEncryptMessage(publicKeys, this._privateKey, plaintext, callback);
} else {
// if no public keys are available encrypt for myself
openpgp.signAndEncryptMessage([this._publicKey], this._privateKey, plaintext, callback);
}
};
/**
* Decrypts a ciphertext
* @param {String} ciphertext The encrypted PGP message block
* @param {String} publicKeyArmored The public key used to sign the message
* @param {Function} callback(error, plaintext, signaturesValid) signaturesValid is undefined in case there are no signature, null in case there are signatures but the wrong public key or no key was used to verify, true if the signature was successfully verified, or false if the signataure verification failed.
*/
PGP.prototype.decrypt = function(ciphertext, publicKeyArmored, callback) {
var publicKeys, message;
// check keys
if (!this._privateKey) {
callback(new Error('Error decrypting. Keys must be set!'));
return;
}
// read keys and ciphertext message
try {
if (publicKeyArmored) {
// parse public keys if available ...
publicKeys = openpgp.key.readArmored(publicKeyArmored).keys;
} else {
// use own public key to know if signatures are available
publicKeys = [this._publicKey];
}
message = openpgp.message.readArmored(ciphertext);
} catch (err) {
callback(new Error('Error parsing encrypted PGP message!'));
return;
}
// decrypt and verify pgp message
openpgp.decryptAndVerifyMessage(this._privateKey, publicKeys, message, onDecrypted);
function onDecrypted(err, decrypted) {
if (err) {
callback(new Error('Error decrypting and verifying PGP message!'));
return;
}
// return decrypted plaintext
callback(null, decrypted.text, checkSignatureValidity(decrypted.signatures));
}
};
/**
* Verifies a clearsigned message
* @param {String} clearSignedText The clearsigned text, usually from a signed pgp/inline message
* @param {String} publicKeyArmored The public key used to signed the message
* @param {Function} callback(error, signaturesValid) signaturesValid is undefined in case there are no signature, null in case there are signatures but the wrong public key or no key was used to verify, true if the signature was successfully verified, or false if the signataure verification failed.
*/
PGP.prototype.verifyClearSignedMessage = function(clearSignedText, publicKeyArmored, callback) {
var publicKeys,
message;
// check keys
if (!this._privateKey) {
callback(new Error('Error verifying signed PGP message. Keys must be set!'));
return;
}
// read keys and ciphertext message
try {
if (publicKeyArmored) {
// parse public keys if available ...
publicKeys = openpgp.key.readArmored(publicKeyArmored).keys;
} else {
// use own public key to know if signatures are available
publicKeys = [this._publicKey];
}
message = openpgp.cleartext.readArmored(clearSignedText);
} catch (err) {
callback(new Error('Error verifying signed PGP message!'));
return;
}
openpgp.verifyClearSignedMessage(publicKeys, message, function(err, result) {
if (err) {
callback(new Error('Error verifying PGP message!'));
return;
}
callback(null, checkSignatureValidity(result.signatures));
});
};
/**
* Verifies a message with a detached signature
* @param {String} message The signed text, usually from a signed pgp/mime message
* @param {String} pgpSignature The detached signature, usually from a signed pgp/mime message
* @param {String} publicKeyArmored The public key used to signed the message
* @param {Function} callback(error, signaturesValid) signaturesValid is undefined in case there are no signature, null in case there are signatures but the wrong public key or no key was used to verify, true if the signature was successfully verified, or false if the signataure verification failed.
*/
PGP.prototype.verifySignedMessage = function(message, pgpSignature, publicKeyArmored, callback) {
var publicKeys;
// check keys
if (!this._privateKey) {
callback(new Error('Error verifying signed PGP message. Keys must be set!'));
return;
}
// read keys and ciphertext message
try {
if (publicKeyArmored) {
// parse public keys if available ...
publicKeys = openpgp.key.readArmored(publicKeyArmored).keys;
} else {
// use own public key to know if signatures are available
publicKeys = [this._publicKey];
}
} catch (err) {
callback(new Error('Error verifying signed PGP message!'));
return;
}
var signatures;
try {
var msg = openpgp.message.readSignedContent(message, pgpSignature);
signatures = msg.verify(publicKeys);
} catch (err) {
callback(new Error('Error verifying signed PGP message!'));
return;
}
callback(null, checkSignatureValidity(signatures));
};
/**
* Checks signature validity
* @param {Object} decrypted OpenPGP.js Signature array
* @return {undefined|null|true|false}
* If signatures array is empty (the message was not signed), returns undefined
* If you're using the wrong public key, returns null.
* If signatures are invalid, returns false.
* If everything is in order, returns true
*/
function checkSignatureValidity(signatures) {
if (!signatures.length) {
// signatures array is empty (the message was not signed)
return;
}
for (var i = 0; i < signatures.length; i++) {
if (signatures[i].valid !== true) { // null | false
// you're using the wrong public key or signatures are invalid
return signatures[i].valid;
}
}
// everything is in order
return true;
/**
* Show a user's fingerprint
*/
PGP.prototype.getFingerprint = function(keyArmored) {
function fingerprint(key) {
return key.primaryKey.getFingerprint().toUpperCase();
}
return PGP;
});
// process armored key input
if (keyArmored) {
return fingerprint(openpgp.key.readArmored(keyArmored).keys[0]);
}
if (!this._publicKey) {
throw new Error('No public key set for fingerprint generation!');
}
// get local fingerpring
return fingerprint(this._publicKey);
};
/**
* Show a user's key id.
*/
PGP.prototype.getKeyId = function(keyArmored) {
var key, pubKeyId, privKeyId;
// process armored key input
if (keyArmored) {
key = openpgp.key.readArmored(keyArmored).keys[0];
return key.primaryKey.getKeyId().toHex().toUpperCase();
}
// check already imported keys
if (!this._privateKey || !this._publicKey) {
throw new Error('Cannot read key IDs... keys not set!');
}
pubKeyId = this._publicKey.primaryKey.getKeyId().toHex().toUpperCase();
privKeyId = this._privateKey.primaryKey.getKeyId().toHex().toUpperCase();
if (!pubKeyId || !privKeyId || pubKeyId !== privKeyId) {
throw new Error('Key IDs do not match!');
}
return pubKeyId;
};
/**
* Read all relevant params of an armored key.
*/
PGP.prototype.getKeyParams = function(keyArmored) {
var key, packet, userIds;
// process armored key input
if (keyArmored) {
key = openpgp.key.readArmored(keyArmored).keys[0];
} else if (this._publicKey) {
key = this._publicKey;
} else {
throw new Error('Cannot read key params... keys not set!');
}
packet = key.primaryKey;
// read user names and email addresses
userIds = [];
key.getUserIds().forEach(function(userId) {
userIds.push({
name: userId.split('<')[0].trim(),
emailAddress: userId.split('<')[1].split('>')[0].trim()
});
});
return {
_id: packet.getKeyId().toHex().toUpperCase(),
userId: userIds[0].emailAddress, // the primary (first) email address of the key
userIds: userIds, // a dictonary of all the key's name/address pairs
fingerprint: packet.getFingerprint().toUpperCase(),
algorithm: packet.algorithm,
bitSize: packet.getBitSize(),
created: packet.created,
};
};
/**
* Extract a public key from a private key
* @param {String} privateKeyArmored The private PGP key block
* @return {String} The publick PGP key block
*/
PGP.prototype.extractPublicKey = function(privateKeyArmored) {
var privkey = openpgp.key.readArmored(privateKeyArmored).keys[0];
var pubkey = privkey.toPublic();
return pubkey.armor();
};
/**
* Import the user's key pair
*/
PGP.prototype.importKeys = function(options, callback) {
var pubKeyId, privKeyId, self = this;
// check options
if (!options.privateKeyArmored || !options.publicKeyArmored) {
callback(new Error('Importing keys failed. Not all options set!'));
return;
}
function resetKeys() {
self._publicKey = undefined;
self._privateKey = undefined;
}
// read armored keys
try {
this._publicKey = openpgp.key.readArmored(options.publicKeyArmored).keys[0];
this._privateKey = openpgp.key.readArmored(options.privateKeyArmored).keys[0];
} catch (e) {
resetKeys();
callback(new Error('Importing keys failed. Parsing error!'));
return;
}
// decrypt private key with passphrase
if (!this._privateKey.decrypt(options.passphrase)) {
resetKeys();
callback(new Error('Incorrect passphrase!'));
return;
}
// check if keys have the same id
pubKeyId = this._publicKey.primaryKey.getKeyId().toHex();
privKeyId = this._privateKey.primaryKey.getKeyId().toHex();
if (!pubKeyId || !privKeyId || pubKeyId !== privKeyId) {
resetKeys();
callback(new Error('Key IDs dont match!'));
return;
}
callback();
};
/**
* Export the user's key pair
*/
PGP.prototype.exportKeys = function(callback) {
if (!this._publicKey || !this._privateKey) {
callback(new Error('Could not export keys!'));
return;
}
callback(null, {
keyId: this._publicKey.primaryKey.getKeyId().toHex().toUpperCase(),
privateKeyArmored: this._privateKey.armor(),
publicKeyArmored: this._publicKey.armor()
});
};
/**
* Change the passphrase of an ascii armored private key.
*/
PGP.prototype.changePassphrase = function(options, callback) {
var privKey, packets, newPassphrase, newKeyArmored;
// set undefined instead of empty string as passphrase
newPassphrase = (options.newPassphrase) ? options.newPassphrase : undefined;
if (!options.privateKeyArmored) {
callback(new Error('Private key must be specified to change passphrase!'));
return;
}
if (options.oldPassphrase === newPassphrase ||
(!options.oldPassphrase && !newPassphrase)) {
callback(new Error('New and old passphrase are the same!'));
return;
}
// read armored key
try {
privKey = openpgp.key.readArmored(options.privateKeyArmored).keys[0];
} catch (e) {
callback(new Error('Importing key failed. Parsing error!'));
return;
}
// decrypt private key with passphrase
if (!privKey.decrypt(options.oldPassphrase)) {
callback(new Error('Old passphrase incorrect!'));
return;
}
// encrypt key with new passphrase
try {
packets = privKey.getAllKeyPackets();
for (var i = 0; i < packets.length; i++) {
packets[i].encrypt(newPassphrase);
}
newKeyArmored = privKey.armor();
} catch (e) {
callback(new Error('Setting new passphrase failed!'));
return;
}
// check if new passphrase really works
if (!privKey.decrypt(newPassphrase)) {
callback(new Error('Decrypting key with new passphrase failed!'));
return;
}
callback(null, newKeyArmored);
};
/**
* Encrypt and sign a pgp message for a list of receivers
*/
PGP.prototype.encrypt = function(plaintext, publicKeysArmored, callback) {
var publicKeys;
// check keys
if (!this._privateKey) {
callback(new Error('Error encrypting. Keys must be set!'));
return;
}
// parse armored public keys
try {
if (publicKeysArmored && publicKeysArmored.length) {
publicKeys = [];
publicKeysArmored.forEach(function(pubkeyArmored) {
publicKeys = publicKeys.concat(openpgp.key.readArmored(pubkeyArmored).keys);
});
}
} catch (err) {
callback(new Error('Error encrypting plaintext!'));
return;
}
if (publicKeys) {
// encrypt and sign the plaintext
openpgp.signAndEncryptMessage(publicKeys, this._privateKey, plaintext, callback);
} else {
// if no public keys are available encrypt for myself
openpgp.signAndEncryptMessage([this._publicKey], this._privateKey, plaintext, callback);
}
};
/**
* Decrypts a ciphertext
* @param {String} ciphertext The encrypted PGP message block
* @param {String} publicKeyArmored The public key used to sign the message
* @param {Function} callback(error, plaintext, signaturesValid) signaturesValid is undefined in case there are no signature, null in case there are signatures but the wrong public key or no key was used to verify, true if the signature was successfully verified, or false if the signataure verification failed.
*/
PGP.prototype.decrypt = function(ciphertext, publicKeyArmored, callback) {
var publicKeys, message;
// check keys
if (!this._privateKey) {
callback(new Error('Error decrypting. Keys must be set!'));
return;
}
// read keys and ciphertext message
try {
if (publicKeyArmored) {
// parse public keys if available ...
publicKeys = openpgp.key.readArmored(publicKeyArmored).keys;
} else {
// use own public key to know if signatures are available
publicKeys = [this._publicKey];
}
message = openpgp.message.readArmored(ciphertext);
} catch (err) {
callback(new Error('Error parsing encrypted PGP message!'));
return;
}
// decrypt and verify pgp message
openpgp.decryptAndVerifyMessage(this._privateKey, publicKeys, message, onDecrypted);
function onDecrypted(err, decrypted) {
if (err) {
callback(new Error('Error decrypting and verifying PGP message!'));
return;
}
// return decrypted plaintext
callback(null, decrypted.text, checkSignatureValidity(decrypted.signatures));
}
};
/**
* Verifies a clearsigned message
* @param {String} clearSignedText The clearsigned text, usually from a signed pgp/inline message
* @param {String} publicKeyArmored The public key used to signed the message
* @param {Function} callback(error, signaturesValid) signaturesValid is undefined in case there are no signature, null in case there are signatures but the wrong public key or no key was used to verify, true if the signature was successfully verified, or false if the signataure verification failed.
*/
PGP.prototype.verifyClearSignedMessage = function(clearSignedText, publicKeyArmored, callback) {
var publicKeys,
message;
// check keys
if (!this._privateKey) {
callback(new Error('Error verifying signed PGP message. Keys must be set!'));
return;
}
// read keys and ciphertext message
try {
if (publicKeyArmored) {
// parse public keys if available ...
publicKeys = openpgp.key.readArmored(publicKeyArmored).keys;
} else {
// use own public key to know if signatures are available
publicKeys = [this._publicKey];
}
message = openpgp.cleartext.readArmored(clearSignedText);
} catch (err) {
callback(new Error('Error verifying signed PGP message!'));
return;
}
openpgp.verifyClearSignedMessage(publicKeys, message, function(err, result) {
if (err) {
callback(new Error('Error verifying PGP message!'));
return;
}
callback(null, checkSignatureValidity(result.signatures));
});
};
/**
* Verifies a message with a detached signature
* @param {String} message The signed text, usually from a signed pgp/mime message
* @param {String} pgpSignature The detached signature, usually from a signed pgp/mime message
* @param {String} publicKeyArmored The public key used to signed the message
* @param {Function} callback(error, signaturesValid) signaturesValid is undefined in case there are no signature, null in case there are signatures but the wrong public key or no key was used to verify, true if the signature was successfully verified, or false if the signataure verification failed.
*/
PGP.prototype.verifySignedMessage = function(message, pgpSignature, publicKeyArmored, callback) {
var publicKeys;
// check keys
if (!this._privateKey) {
callback(new Error('Error verifying signed PGP message. Keys must be set!'));
return;
}
// read keys and ciphertext message
try {
if (publicKeyArmored) {
// parse public keys if available ...
publicKeys = openpgp.key.readArmored(publicKeyArmored).keys;
} else {
// use own public key to know if signatures are available
publicKeys = [this._publicKey];
}
} catch (err) {
callback(new Error('Error verifying signed PGP message!'));
return;
}
var signatures;
try {
var msg = openpgp.message.readSignedContent(message, pgpSignature);
signatures = msg.verify(publicKeys);
} catch (err) {
callback(new Error('Error verifying signed PGP message!'));
return;
}
callback(null, checkSignatureValidity(signatures));
};
/**
* Checks signature validity
* @param {Object} decrypted OpenPGP.js Signature array
* @return {undefined|null|true|false}
* If signatures array is empty (the message was not signed), returns undefined
* If you're using the wrong public key, returns null.
* If signatures are invalid, returns false.
* If everything is in order, returns true
*/
function checkSignatureValidity(signatures) {
if (!signatures.length) {
// signatures array is empty (the message was not signed)
return;
}
for (var i = 0; i < signatures.length; i++) {
if (signatures[i].valid !== true) { // null | false
// you're using the wrong public key or signatures are invalid
return signatures[i].valid;
}
}
// everything is in order
return true;
}
module.exports = PGP;

View File

@ -1,63 +1,61 @@
define(function() {
'use strict';
'use strict';
var AdminDAO = function(restDao) {
this._restDao = restDao;
};
var AdminDAO = function(restDao) {
this._restDao = restDao;
};
/**
* Create a new email account.
* @param {String} options.emailAddress The desired email address
* @param {String} options.password The password to be used for the account.
* @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) {
var uri;
/**
* Create a new email account.
* @param {String} options.emailAddress The desired email address
* @param {String} options.password The password to be used for the account.
* @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) {
var uri;
if (!options.emailAddress || !options.password || !options.phone) {
callback(new Error('Incomplete arguments!'));
if (!options.emailAddress || !options.password || !options.phone) {
callback(new Error('Incomplete arguments!'));
return;
}
uri = '/user';
this._restDao.post(options, uri, function(err) {
if (err && err.code === 409) {
callback(new Error('User name is already taken!'));
return;
} else if (err) {
callback(new Error('Error creating new user!'));
return;
}
uri = '/user';
this._restDao.post(options, uri, function(err) {
if (err && err.code === 409) {
callback(new Error('User name is already taken!'));
return;
} else if (err) {
callback(new Error('Error creating new user!'));
return;
}
callback();
});
};
/**
* Verify a user's phone number by confirming a token to the server.
* @param {String} options.emailAddress The desired email address
* @param {String} options.token The validation token.
* @param {Function} callback(error)
*/
AdminDAO.prototype.validateUser = function(options, callback) {
var uri;
if (!options.emailAddress || !options.token) {
callback(new Error('Incomplete arguments!'));
return;
}
uri = '/user/validate';
this._restDao.post(options, uri, function(err) {
if (!err || (err && err.code === 202)) {
// success
callback();
});
};
/**
* Verify a user's phone number by confirming a token to the server.
* @param {String} options.emailAddress The desired email address
* @param {String} options.token The validation token.
* @param {Function} callback(error)
*/
AdminDAO.prototype.validateUser = function(options, callback) {
var uri;
if (!options.emailAddress || !options.token) {
callback(new Error('Incomplete arguments!'));
return;
} else {
callback(new Error('Validation failed!'));
}
});
};
uri = '/user/validate';
this._restDao.post(options, uri, function(err) {
if (!err || (err && err.code === 202)) {
// success
callback();
} else {
callback(new Error('Validation failed!'));
}
});
};
return AdminDAO;
});
module.exports = AdminDAO;

View File

@ -1,97 +1,93 @@
/**
* High level storage api that handles all persistence on the device. If
* SQLcipher/SQLite is available, all data is securely persisted there,
* through transparent encryption. If not, the crypto API is
* used to encrypt data on the fly before persisting via a JSON store.
* High level storage api that handles all persistence on the device.
*/
define(function() {
'use strict';
var DeviceStorageDAO = function(localDbDao) {
this._localDbDao = localDbDao;
};
'use strict';
DeviceStorageDAO.prototype.init = function(emailAddress, callback) {
this._localDbDao.init(emailAddress, callback);
};
var DeviceStorageDAO = function(localDbDao) {
this._localDbDao = localDbDao;
};
/**
* Stores a list of encrypted items in the object store
* @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) {
var key, items = [];
DeviceStorageDAO.prototype.init = function(emailAddress, callback) {
this._localDbDao.init(emailAddress, callback);
};
// nothing to store
if (!list || list.length === 0) {
callback();
return;
}
// validate type
if (!type) {
callback({
errMsg: 'Type is not set!'
});
return;
}
/**
* Stores a list of encrypted items in the object store
* @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) {
var key, items = [];
// format items for batch storing in dao
list.forEach(function(i) {
key = createKey(i, type);
items.push({
key: key,
object: i
});
// nothing to store
if (!list || list.length === 0) {
callback();
return;
}
// validate type
if (!type) {
callback({
errMsg: 'Type is not set!'
});
this._localDbDao.batch(items, callback);
};
/**
* Deletes items of a certain type from storage
*/
DeviceStorageDAO.prototype.removeList = function(type, callback) {
this._localDbDao.removeList(type, callback);
};
/**
* List stored items of a given type
* @param type [String] The type of item e.g. 'email'
* @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) {
// fetch all items of a certain type from the data-store
this._localDbDao.list(type, offset, num, callback);
};
/**
* Clear the whole device data-store
*/
DeviceStorageDAO.prototype.clear = function(callback) {
this._localDbDao.clear(callback);
};
//
// helper functions
//
function createKey(i, type) {
var key;
// put uid in key if available... for easy querying
if (i.uid) {
key = type + '_' + i.uid;
} else if (i.id) {
key = type + '_' + i.id;
} else {
key = type;
}
return key;
return;
}
return DeviceStorageDAO;
});
// format items for batch storing in dao
list.forEach(function(i) {
key = createKey(i, type);
items.push({
key: key,
object: i
});
});
this._localDbDao.batch(items, callback);
};
/**
* Deletes items of a certain type from storage
*/
DeviceStorageDAO.prototype.removeList = function(type, callback) {
this._localDbDao.removeList(type, callback);
};
/**
* List stored items of a given type
* @param type [String] The type of item e.g. 'email'
* @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) {
// fetch all items of a certain type from the data-store
this._localDbDao.list(type, offset, num, callback);
};
/**
* Clear the whole device data-store
*/
DeviceStorageDAO.prototype.clear = function(callback) {
this._localDbDao.clear(callback);
};
//
// helper functions
//
function createKey(i, type) {
var key;
// put uid in key if available... for easy querying
if (i.uid) {
key = type + '_' + i.uid;
} else if (i.id) {
key = type + '_' + i.id;
} else {
key = type;
}
return key;
}
module.exports = DeviceStorageDAO;

File diff suppressed because it is too large Load Diff

View File

@ -1,62 +1,60 @@
define(function() {
'use strict';
'use strict';
/**
* The InvitationDAO 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;
};
/**
* The InvitationDAO 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;
};
//
// Constants
//
//
// Constants
//
InvitationDAO.INVITE_MISSING = 1;
InvitationDAO.INVITE_PENDING = 2;
InvitationDAO.INVITE_SUCCESS = 4;
InvitationDAO.INVITE_MISSING = 1;
InvitationDAO.INVITE_PENDING = 2;
InvitationDAO.INVITE_SUCCESS = 4;
//
// API
//
//
// API
//
/**
* Notes an invite for the recipient by the sender in the invitation web service
* @param {String} options.recipient User ID of the recipient
* @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) {
if (typeof options !== 'object' || typeof options.recipient !== 'string' || typeof options.recipient !== 'string') {
callback({
errMsg: 'erroneous usage of api: incorrect parameters!'
});
/**
* Notes an invite for the recipient by the sender in the invitation web service
* @param {String} options.recipient User ID of the recipient
* @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) {
if (typeof options !== 'object' || typeof options.recipient !== 'string' || typeof options.recipient !== 'string') {
callback({
errMsg: 'erroneous usage of api: incorrect parameters!'
});
return;
}
var uri = '/invitation/recipient/' + options.recipient + '/sender/' + options.sender;
this._restDao.put({}, uri, completed);
function completed(error, res, status) {
if (error) {
callback(error);
return;
}
var uri = '/invitation/recipient/' + options.recipient + '/sender/' + options.sender;
this._restDao.put({}, uri, completed);
function completed(error, res, status) {
if (error) {
callback(error);
return;
}
if (status === 201) {
callback(null, InvitationDAO.INVITE_SUCCESS);
return;
} else if (status === 304) {
callback(null, InvitationDAO.INVITE_PENDING);
return;
}
callback({
errMsg: 'unexpected invitation state'
});
if (status === 201) {
callback(null, InvitationDAO.INVITE_SUCCESS);
return;
} else if (status === 304) {
callback(null, InvitationDAO.INVITE_PENDING);
return;
}
};
return InvitationDAO;
});
callback({
errMsg: 'unexpected invitation state'
});
}
};
module.exports = InvitationDAO;

File diff suppressed because it is too large Load Diff

View File

@ -1,222 +1,216 @@
/**
* Handles generic caching of JSON objects in a lawnchair adapter
*/
define(function(require) {
'use strict';
var _ = require('underscore'),
Lawnchair = require('lawnchair');
require('lawnchairSQL');
require('lawnchairIDB');
'use strict';
var LawnchairDAO = function() {};
var LawnchairDAO = function() {};
LawnchairDAO.prototype.init = function(dbName, callback) {
if (!dbName) {
LawnchairDAO.prototype.init = function(dbName, callback) {
if (!dbName) {
callback({
errMsg: 'Lawnchair DB name must be specified!'
});
return;
}
this._db = new Lawnchair({
name: dbName
}, function(lc) {
if (!lc) {
callback({
errMsg: 'Lawnchair DB name must be specified!'
errMsg: 'Lawnchair init failed!'
});
return;
}
this._db = new Lawnchair({
name: dbName
}, function(lc) {
if (!lc) {
callback({
errMsg: 'Lawnchair init failed!'
});
return;
}
callback();
});
};
/**
* Create or update an object
*/
LawnchairDAO.prototype.persist = function(key, object, callback) {
if (!key || !object) {
callback({
errMsg: 'Key and Object must be set!'
});
return;
}
this._db.save({
key: key,
object: object
}, function(persisted) {
if (persisted.key !== key) {
callback({
errMsg: 'Persisting failed!'
});
return;
}
callback();
});
};
/**
* Persist a bunch of items at once
*/
LawnchairDAO.prototype.batch = function(list, callback) {
if (!(list instanceof Array)) {
callback({
errMsg: 'Input must be of type Array!'
});
return;
}
this._db.batch(list, function(res) {
if (!res) {
callback({
errMsg: 'Persisting batch failed!'
});
return;
}
callback();
});
};
/**
* Read a single item by its key
*/
LawnchairDAO.prototype.read = function(key, callback) {
if (!key) {
callback({
errMsg: 'Key must be specified!'
});
return;
}
this._db.get(key, function(o) {
if (o) {
callback(null, o.object);
} else {
callback();
});
};
}
});
};
/**
* Create or update an object
*/
LawnchairDAO.prototype.persist = function(key, object, callback) {
if (!key || !object) {
callback({
errMsg: 'Key and Object must be set!'
});
/**
* List all the items of a certain type
* @param type [String] The type of item e.g. 'email'
* @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)
*/
LawnchairDAO.prototype.list = function(type, offset, num, callback) {
var self = this,
i, from, to,
matchingKeys = [],
intervalKeys = [],
list = [];
// validate input
if (!type || typeof offset === 'undefined' || typeof num === 'undefined') {
callback({
errMsg: 'Args not is not set!'
});
return;
}
// get all keys
self._db.keys(function(keys) {
// check if key begins with type
keys.forEach(function(key) {
if (key.indexOf(type) === 0) {
matchingKeys.push(key);
}
});
// sort keys
matchingKeys.sort();
// set window of items to fetch
// if num is null, list all items
from = (num) ? matchingKeys.length - offset - num : 0;
to = matchingKeys.length - 1 - offset;
// filter items within requested interval
for (i = 0; i < matchingKeys.length; i++) {
if (i >= from && i <= to) {
intervalKeys.push(matchingKeys[i]);
}
}
// return if there are no matching keys
if (intervalKeys.length === 0) {
callback(null, list);
return;
}
this._db.save({
key: key,
object: object
}, function(persisted) {
if (persisted.key !== key) {
callback({
errMsg: 'Persisting failed!'
});
return;
}
// fetch all items from data-store with matching key
self._db.get(intervalKeys, function(intervalList) {
intervalList.forEach(function(item) {
list.push(item.object);
});
// return only the interval between offset and num
callback(null, list);
});
});
};
/**
* Removes an object liter from local storage by its key (delete)
*/
LawnchairDAO.prototype.remove = function(key, callback) {
this._db.remove(key, callback);
};
/**
* Removes an object liter from local storage by its key (delete)
*/
LawnchairDAO.prototype.removeList = function(type, callback) {
var self = this,
matchingKeys = [],
after;
// validate type
if (!type) {
callback({
errMsg: 'Type is not set!'
});
return;
}
// get all keys
self._db.keys(function(keys) {
// check if key begins with type
keys.forEach(function(key) {
if (key.indexOf(type) === 0) {
matchingKeys.push(key);
}
});
if (matchingKeys.length < 1) {
callback();
});
};
/**
* Persist a bunch of items at once
*/
LawnchairDAO.prototype.batch = function(list, callback) {
if (!(list instanceof Array)) {
callback({
errMsg: 'Input must be of type Array!'
});
return;
}
this._db.batch(list, function(res) {
if (!res) {
callback({
errMsg: 'Persisting batch failed!'
});
return;
}
callback();
// remove all matching keys
after = _.after(matchingKeys.length, callback);
_.each(matchingKeys, function(key) {
self._db.remove(key, after);
});
};
});
};
/**
* Read a single item by its key
*/
LawnchairDAO.prototype.read = function(key, callback) {
if (!key) {
callback({
errMsg: 'Key must be specified!'
});
return;
}
/**
* Clears the whole local storage cache
*/
LawnchairDAO.prototype.clear = function(callback) {
this._db.nuke(callback);
};
this._db.get(key, function(o) {
if (o) {
callback(null, o.object);
} else {
callback();
}
});
};
/**
* List all the items of a certain type
* @param type [String] The type of item e.g. 'email'
* @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)
*/
LawnchairDAO.prototype.list = function(type, offset, num, callback) {
var self = this,
i, from, to,
matchingKeys = [],
intervalKeys = [],
list = [];
// validate input
if (!type || typeof offset === 'undefined' || typeof num === 'undefined') {
callback({
errMsg: 'Args not is not set!'
});
return;
}
// get all keys
self._db.keys(function(keys) {
// check if key begins with type
keys.forEach(function(key) {
if (key.indexOf(type) === 0) {
matchingKeys.push(key);
}
});
// sort keys
matchingKeys.sort();
// set window of items to fetch
// if num is null, list all items
from = (num) ? matchingKeys.length - offset - num : 0;
to = matchingKeys.length - 1 - offset;
// filter items within requested interval
for (i = 0; i < matchingKeys.length; i++) {
if (i >= from && i <= to) {
intervalKeys.push(matchingKeys[i]);
}
}
// return if there are no matching keys
if (intervalKeys.length === 0) {
callback(null, list);
return;
}
// fetch all items from data-store with matching key
self._db.get(intervalKeys, function(intervalList) {
intervalList.forEach(function(item) {
list.push(item.object);
});
// return only the interval between offset and num
callback(null, list);
});
});
};
/**
* Removes an object liter from local storage by its key (delete)
*/
LawnchairDAO.prototype.remove = function(key, callback) {
this._db.remove(key, callback);
};
/**
* Removes an object liter from local storage by its key (delete)
*/
LawnchairDAO.prototype.removeList = function(type, callback) {
var self = this,
matchingKeys = [],
after;
// validate type
if (!type) {
callback({
errMsg: 'Type is not set!'
});
return;
}
// get all keys
self._db.keys(function(keys) {
// check if key begins with type
keys.forEach(function(key) {
if (key.indexOf(type) === 0) {
matchingKeys.push(key);
}
});
if (matchingKeys.length < 1) {
callback();
return;
}
// remove all matching keys
after = _.after(matchingKeys.length, callback);
_.each(matchingKeys, function(key) {
self._db.remove(key, after);
});
});
};
/**
* Clears the whole local storage cache
*/
LawnchairDAO.prototype.clear = function(callback) {
this._db.nuke(callback);
};
return LawnchairDAO;
});
module.exports = LawnchairDAO;

View File

@ -1,198 +1,196 @@
define(function() {
'use strict';
'use strict';
var PrivateKeyDAO = function(restDao) {
this._restDao = restDao;
};
var PrivateKeyDAO = function(restDao) {
this._restDao = restDao;
};
//
// Device registration functions
//
//
// Device registration functions
//
/**
* Request registration of a new device by fetching registration session key.
* @param {String} options.userId The user's email address
* @param {String} options.deviceName The device's memorable name
* @param {Function} callback(error, regSessionKey)
* @return {Object} {encryptedRegSessionKey:[base64]}
*/
PrivateKeyDAO.prototype.requestDeviceRegistration = function(options, callback) {
var uri;
/**
* Request registration of a new device by fetching registration session key.
* @param {String} options.userId The user's email address
* @param {String} options.deviceName The device's memorable name
* @param {Function} callback(error, regSessionKey)
* @return {Object} {encryptedRegSessionKey:[base64]}
*/
PrivateKeyDAO.prototype.requestDeviceRegistration = function(options, callback) {
var uri;
if (!options.userId || !options.deviceName) {
callback(new Error('Incomplete arguments!'));
if (!options.userId || !options.deviceName) {
callback(new Error('Incomplete arguments!'));
return;
}
uri = '/device/user/' + options.userId + '/devicename/' + options.deviceName;
this._restDao.post(undefined, uri, callback);
};
/**
* Authenticate device registration by uploading the deviceSecret encrypted with the regSessionKeys.
* @param {String} options.userId The user's email address
* @param {String} options.deviceName The device's memorable name
* @param {String} options.encryptedDeviceSecret The base64 encoded encrypted device secret
* @param {String} options.iv The iv used for encryption
* @param {Function} callback(error)
*/
PrivateKeyDAO.prototype.uploadDeviceSecret = function(options, callback) {
var uri;
if (!options.userId || !options.deviceName || !options.encryptedDeviceSecret || !options.iv) {
callback(new Error('Incomplete arguments!'));
return;
}
uri = '/device/user/' + options.userId + '/devicename/' + options.deviceName;
this._restDao.put(options, uri, callback);
};
//
// Private key functions
//
/**
* Request authSessionKeys required for upload the encrypted private PGP key.
* @param {String} options.userId The user's email address
* @param {Function} callback(error, authSessionKey)
* @return {Object} {sessionId, encryptedAuthSessionKey:[base64 encoded], encryptedChallenge:[base64 encoded]}
*/
PrivateKeyDAO.prototype.requestAuthSessionKey = function(options, callback) {
var uri;
if (!options.userId) {
callback(new Error('Incomplete arguments!'));
return;
}
uri = '/auth/user/' + options.userId;
this._restDao.post(undefined, uri, callback);
};
/**
* Verifiy authentication by uploading the challenge and deviceSecret encrypted with the authSessionKeys as a response.
* @param {String} options.userId The user's email address
* @param {String} options.encryptedChallenge The server's base64 encoded challenge encrypted using the authSessionKey
* @param {String} options.encryptedDeviceSecret The server's base64 encoded deviceSecret encrypted using the authSessionKey
* @param {String} options.iv The iv used for encryption
* @param {Function} callback(error)
*/
PrivateKeyDAO.prototype.verifyAuthentication = function(options, callback) {
var uri;
if (!options.userId || !options.sessionId || !options.encryptedChallenge || !options.encryptedDeviceSecret || !options.iv) {
callback(new Error('Incomplete arguments!'));
return;
}
uri = '/auth/user/' + options.userId + '/session/' + options.sessionId;
this._restDao.put(options, uri, callback);
};
/**
* Upload the encrypted private PGP key.
* @param {String} options._id The hex encoded capital 16 char key id
* @param {String} options.userId The user's email address
* @param {String} options.encryptedPrivateKey The base64 encoded encrypted private PGP key
* @param {String} options.sessionId The session id
* @param {Function} callback(error)
*/
PrivateKeyDAO.prototype.upload = function(options, callback) {
var uri;
if (!options._id || !options.userId || !options.encryptedPrivateKey || !options.sessionId || !options.salt || !options.iv) {
callback(new Error('Incomplete arguments!'));
return;
}
uri = '/privatekey/user/' + options.userId + '/session/' + options.sessionId;
this._restDao.post(options, uri, callback);
};
/**
* Query if an encrypted private PGP key exists on the server without initializing the recovery procedure.
* @param {String} options.userId The user's email address
* @param {String} options.keyId The private PGP key id
* @param {Function} callback(error, found)
* @return {Boolean} whether the key was found on the server or not.
*/
PrivateKeyDAO.prototype.hasPrivateKey = function(options, callback) {
if (!options.userId || !options.keyId) {
callback(new Error('Incomplete arguments!'));
return;
}
this._restDao.get({
uri: '/privatekey/user/' + options.userId + '/key/' + options.keyId + '?ignoreRecovery=true',
}, function(err) {
// 404: there is no encrypted private key on the server
if (err && err.code !== 200) {
callback(null, false);
return;
}
uri = '/device/user/' + options.userId + '/devicename/' + options.deviceName;
this._restDao.post(undefined, uri, callback);
};
/**
* Authenticate device registration by uploading the deviceSecret encrypted with the regSessionKeys.
* @param {String} options.userId The user's email address
* @param {String} options.deviceName The device's memorable name
* @param {String} options.encryptedDeviceSecret The base64 encoded encrypted device secret
* @param {String} options.iv The iv used for encryption
* @param {Function} callback(error)
*/
PrivateKeyDAO.prototype.uploadDeviceSecret = function(options, callback) {
var uri;
if (!options.userId || !options.deviceName || !options.encryptedDeviceSecret || !options.iv) {
callback(new Error('Incomplete arguments!'));
if (err) {
callback(err);
return;
}
uri = '/device/user/' + options.userId + '/devicename/' + options.deviceName;
this._restDao.put(options, uri, callback);
};
callback(null, true);
});
};
//
// Private key functions
//
/**
* Request download for the encrypted private PGP key.
* @param {String} options.userId The user's email address
* @param {String} options.keyId The private PGP key id
* @param {Function} callback(error, found)
* @return {Boolean} whether the key was found on the server or not.
*/
PrivateKeyDAO.prototype.requestDownload = function(options, callback) {
if (!options.userId || !options.keyId) {
callback(new Error('Incomplete arguments!'));
return;
}
/**
* Request authSessionKeys required for upload the encrypted private PGP key.
* @param {String} options.userId The user's email address
* @param {Function} callback(error, authSessionKey)
* @return {Object} {sessionId, encryptedAuthSessionKey:[base64 encoded], encryptedChallenge:[base64 encoded]}
*/
PrivateKeyDAO.prototype.requestAuthSessionKey = function(options, callback) {
var uri;
if (!options.userId) {
callback(new Error('Incomplete arguments!'));
this._restDao.get({
uri: '/privatekey/user/' + options.userId + '/key/' + options.keyId
}, function(err) {
// 404: there is no encrypted private key on the server
if (err && err.code !== 200) {
callback(null, false);
return;
}
uri = '/auth/user/' + options.userId;
this._restDao.post(undefined, uri, callback);
};
/**
* Verifiy authentication by uploading the challenge and deviceSecret encrypted with the authSessionKeys as a response.
* @param {String} options.userId The user's email address
* @param {String} options.encryptedChallenge The server's base64 encoded challenge encrypted using the authSessionKey
* @param {String} options.encryptedDeviceSecret The server's base64 encoded deviceSecret encrypted using the authSessionKey
* @param {String} options.iv The iv used for encryption
* @param {Function} callback(error)
*/
PrivateKeyDAO.prototype.verifyAuthentication = function(options, callback) {
var uri;
if (!options.userId || !options.sessionId || !options.encryptedChallenge || !options.encryptedDeviceSecret || !options.iv) {
callback(new Error('Incomplete arguments!'));
if (err) {
callback(err);
return;
}
uri = '/auth/user/' + options.userId + '/session/' + options.sessionId;
this._restDao.put(options, uri, callback);
};
callback(null, true);
});
};
/**
* Upload the encrypted private PGP key.
* @param {String} options._id The hex encoded capital 16 char key id
* @param {String} options.userId The user's email address
* @param {String} options.encryptedPrivateKey The base64 encoded encrypted private PGP key
* @param {String} options.sessionId The session id
* @param {Function} callback(error)
*/
PrivateKeyDAO.prototype.upload = function(options, callback) {
var uri;
/**
* Verify the download request for the private PGP key using the recovery token sent via email. This downloads the actual encrypted private key.
* @param {String} options.userId The user's email address
* @param {String} options.keyId The private key id
* @param {String} options.recoveryToken The token proving the user own the email account
* @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) {
var uri;
if (!options._id || !options.userId || !options.encryptedPrivateKey || !options.sessionId || !options.salt || !options.iv) {
callback(new Error('Incomplete arguments!'));
return;
}
if (!options.userId || !options.keyId || !options.recoveryToken) {
callback(new Error('Incomplete arguments!'));
return;
}
uri = '/privatekey/user/' + options.userId + '/session/' + options.sessionId;
this._restDao.post(options, uri, callback);
};
uri = '/privatekey/user/' + options.userId + '/key/' + options.keyId + '/recovery/' + options.recoveryToken;
this._restDao.get({
uri: uri
}, callback);
};
/**
* Query if an encrypted private PGP key exists on the server without initializing the recovery procedure.
* @param {String} options.userId The user's email address
* @param {String} options.keyId The private PGP key id
* @param {Function} callback(error, found)
* @return {Boolean} whether the key was found on the server or not.
*/
PrivateKeyDAO.prototype.hasPrivateKey = function(options, callback) {
if (!options.userId || !options.keyId) {
callback(new Error('Incomplete arguments!'));
return;
}
this._restDao.get({
uri: '/privatekey/user/' + options.userId + '/key/' + options.keyId + '?ignoreRecovery=true',
}, function(err) {
// 404: there is no encrypted private key on the server
if (err && err.code !== 200) {
callback(null, false);
return;
}
if (err) {
callback(err);
return;
}
callback(null, true);
});
};
/**
* Request download for the encrypted private PGP key.
* @param {String} options.userId The user's email address
* @param {String} options.keyId The private PGP key id
* @param {Function} callback(error, found)
* @return {Boolean} whether the key was found on the server or not.
*/
PrivateKeyDAO.prototype.requestDownload = function(options, callback) {
if (!options.userId || !options.keyId) {
callback(new Error('Incomplete arguments!'));
return;
}
this._restDao.get({
uri: '/privatekey/user/' + options.userId + '/key/' + options.keyId
}, function(err) {
// 404: there is no encrypted private key on the server
if (err && err.code !== 200) {
callback(null, false);
return;
}
if (err) {
callback(err);
return;
}
callback(null, true);
});
};
/**
* Verify the download request for the private PGP key using the recovery token sent via email. This downloads the actual encrypted private key.
* @param {String} options.userId The user's email address
* @param {String} options.keyId The private key id
* @param {String} options.recoveryToken The token proving the user own the email account
* @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) {
var uri;
if (!options.userId || !options.keyId || !options.recoveryToken) {
callback(new Error('Incomplete arguments!'));
return;
}
uri = '/privatekey/user/' + options.userId + '/key/' + options.keyId + '/recovery/' + options.recoveryToken;
this._restDao.get({
uri: uri
}, callback);
};
return PrivateKeyDAO;
});
module.exports = PrivateKeyDAO;

View File

@ -1,105 +1,103 @@
define(function() {
'use strict';
'use strict';
var PublicKeyDAO = function(restDao) {
this._restDao = restDao;
};
var PublicKeyDAO = function(restDao) {
this._restDao = restDao;
};
/**
* Verify the public key behind the given uuid
*/
PublicKeyDAO.prototype.verify = function(uuid, callback) {
var uri = '/verify/' + uuid;
/**
* Verify the public key behind the given uuid
*/
PublicKeyDAO.prototype.verify = function(uuid, callback) {
var uri = '/verify/' + uuid;
this._restDao.get({
uri: uri,
type: 'text'
}, function(err, res, status) {
if (err && err.code === 400) {
// there was an attempt to verify a non-existing public key
callback();
return;
}
this._restDao.get({
uri: uri,
type: 'text'
}, function(err, res, status) {
if (err && err.code === 400) {
// there was an attempt to verify a non-existing public key
callback();
return;
}
callback(err, res, status);
});
};
callback(err, res, status);
});
};
/**
* Find the user's corresponding public key
*/
PublicKeyDAO.prototype.get = function(keyId, callback) {
var uri = '/publickey/key/' + keyId;
/**
* Find the user's corresponding public key
*/
PublicKeyDAO.prototype.get = function(keyId, callback) {
var uri = '/publickey/key/' + keyId;
this._restDao.get({
uri: uri
}, function(err, key) {
if (err && err.code === 404) {
callback();
return;
}
this._restDao.get({
uri: uri
}, function(err, key) {
if (err && err.code === 404) {
callback();
return;
}
if (err) {
callback(err);
return;
}
if (err) {
callback(err);
return;
}
callback(null, (key && key._id) ? key : undefined);
});
};
callback(null, (key && key._id) ? key : undefined);
});
};
/**
* Find the user's corresponding public key by email
*/
PublicKeyDAO.prototype.getByUserId = function(userId, callback) {
var uri = '/publickey/user/' + userId;
/**
* Find the user's corresponding public key by email
*/
PublicKeyDAO.prototype.getByUserId = function(userId, callback) {
var uri = '/publickey/user/' + userId;
this._restDao.get({
uri: uri
}, function(err, keys) {
// not found
if (err && err.code === 404) {
callback();
return;
}
this._restDao.get({
uri: uri
}, function(err, keys) {
// not found
if (err && err.code === 404) {
callback();
return;
}
if (err) {
callback(err);
return;
}
if (err) {
callback(err);
return;
}
if (!keys || keys.length < 1) {
// 'No public key for that user!'
callback();
return;
}
if (!keys || keys.length < 1) {
// 'No public key for that user!'
callback();
return;
}
if (keys.length > 1) {
callback({
errMsg: 'That user has multiple public keys!'
});
return;
}
if (keys.length > 1) {
callback({
errMsg: 'That user has multiple public keys!'
});
return;
}
callback(null, keys[0]);
});
};
callback(null, keys[0]);
});
};
/**
* Persist the user's publc key
*/
PublicKeyDAO.prototype.put = function(pubkey, callback) {
var uri = '/publickey/user/' + pubkey.userId + '/key/' + pubkey._id;
this._restDao.put(pubkey, uri, callback);
};
/**
* Persist the user's publc key
*/
PublicKeyDAO.prototype.put = function(pubkey, callback) {
var uri = '/publickey/user/' + pubkey.userId + '/key/' + pubkey._id;
this._restDao.put(pubkey, uri, callback);
};
/**
* Delete the public key from the cloud storage service
*/
PublicKeyDAO.prototype.remove = function(keyId, callback) {
var uri = '/publickey/key/' + keyId;
this._restDao.remove(uri, callback);
};
/**
* Delete the public key from the cloud storage service
*/
PublicKeyDAO.prototype.remove = function(keyId, callback) {
var uri = '/publickey/key/' + keyId;
this._restDao.remove(uri, callback);
};
return PublicKeyDAO;
});
module.exports = PublicKeyDAO;

View File

@ -1,123 +1,121 @@
define(function(require) {
'use strict';
'use strict';
var config = require('js/app-config').config;
var config = require('../app-config').config;
var RestDAO = function(baseUri) {
if (baseUri) {
this._baseUri = baseUri;
} else {
this._baseUri = config.cloudUrl;
}
};
var RestDAO = function(baseUri) {
if (baseUri) {
this._baseUri = baseUri;
} else {
this._baseUri = config.cloudUrl;
}
};
/**
* GET (read) request
* @param {String} options.uri URI relative to the base uri to perform the GET request with.
* @param {String} options.type (optional) The type of data that you're expecting back from the server: json, xml, text. Default: json.
*/
RestDAO.prototype.get = function(options, callback) {
options.method = 'GET';
this._processRequest(options, callback);
};
/**
* GET (read) request
* @param {String} options.uri URI relative to the base uri to perform the GET request with.
* @param {String} options.type (optional) The type of data that you're expecting back from the server: json, xml, text. Default: json.
*/
RestDAO.prototype.get = function(options, callback) {
options.method = 'GET';
this._processRequest(options, callback);
};
/**
* POST (create) request
*/
RestDAO.prototype.post = function(item, uri, callback) {
this._processRequest({
method: 'POST',
payload: item,
uri: uri
}, callback);
};
/**
* POST (create) request
*/
RestDAO.prototype.post = function(item, uri, callback) {
this._processRequest({
method: 'POST',
payload: item,
uri: uri
}, callback);
};
/**
* PUT (update) request
*/
RestDAO.prototype.put = function(item, uri, callback) {
this._processRequest({
method: 'PUT',
payload: item,
uri: uri
}, callback);
};
/**
* PUT (update) request
*/
RestDAO.prototype.put = function(item, uri, callback) {
this._processRequest({
method: 'PUT',
payload: item,
uri: uri
}, callback);
};
/**
* DELETE (remove) request
*/
RestDAO.prototype.remove = function(uri, callback) {
this._processRequest({
method: 'DELETE',
uri: uri
}, callback);
};
/**
* DELETE (remove) request
*/
RestDAO.prototype.remove = function(uri, callback) {
this._processRequest({
method: 'DELETE',
uri: uri
}, callback);
};
//
// helper functions
//
//
// helper functions
//
RestDAO.prototype._processRequest = function(options, callback) {
var xhr, format;
RestDAO.prototype._processRequest = function(options, callback) {
var xhr, format;
if (typeof options.uri === 'undefined') {
callback({
code: 400,
errMsg: 'Bad Request! URI is a mandatory parameter.'
});
return;
}
if (typeof options.uri === 'undefined') {
callback({
code: 400,
errMsg: 'Bad Request! URI is a mandatory parameter.'
});
return;
}
options.type = options.type || 'json';
options.type = options.type || 'json';
if (options.type === 'json') {
format = 'application/json';
} else if (options.type === 'xml') {
format = 'application/xml';
} else if (options.type === 'text') {
format = 'text/plain';
} else {
callback({
code: 400,
errMsg: 'Bad Request! Unhandled data type.'
});
return;
}
if (options.type === 'json') {
format = 'application/json';
} else if (options.type === 'xml') {
format = 'application/xml';
} else if (options.type === 'text') {
format = 'text/plain';
} else {
callback({
code: 400,
errMsg: 'Bad Request! Unhandled data type.'
});
return;
}
xhr = new XMLHttpRequest();
xhr.open(options.method, this._baseUri + options.uri);
xhr.setRequestHeader('Accept', format);
xhr.setRequestHeader('Content-Type', format);
xhr = new XMLHttpRequest();
xhr.open(options.method, this._baseUri + options.uri);
xhr.setRequestHeader('Accept', format);
xhr.setRequestHeader('Content-Type', format);
xhr.onload = function() {
var res;
xhr.onload = function() {
var res;
if (xhr.readyState === 4 && (xhr.status === 200 || xhr.status === 201 || xhr.status === 304)) {
if (options.type === 'json') {
res = xhr.responseText ? JSON.parse(xhr.responseText) : xhr.responseText;
} else {
res = xhr.responseText;
}
callback(null, res, xhr.status);
return;
if (xhr.readyState === 4 && (xhr.status === 200 || xhr.status === 201 || xhr.status === 304)) {
if (options.type === 'json') {
res = xhr.responseText ? JSON.parse(xhr.responseText) : xhr.responseText;
} else {
res = xhr.responseText;
}
callback({
code: xhr.status,
errMsg: xhr.statusText
});
};
callback(null, res, xhr.status);
return;
}
xhr.onerror = function() {
callback({
code: 42,
errMsg: 'Error calling ' + options.method + ' on ' + options.uri
});
};
xhr.send(options.payload ? JSON.stringify(options.payload) : undefined);
callback({
code: xhr.status,
errMsg: xhr.statusText
});
};
return RestDAO;
});
xhr.onerror = function() {
callback({
code: 42,
errMsg: 'Error calling ' + options.method + ' on ' + options.uri
});
};
xhr.send(options.payload ? JSON.stringify(options.payload) : undefined);
};
module.exports = RestDAO;

View File

@ -1,57 +1,55 @@
define(function(require) {
'use strict';
'use strict';
var axe = require('axe'),
DEBUG_TAG = 'backbutton handler';
var axe = require('axe-logger'),
DEBUG_TAG = 'backbutton handler';
/**
* The back button handler introduces meaningful behavior fo rthe back button:
* if there's an open lightbox, close it;
* if the reader is open in mobile mode, close it;
* if the navigation is open, close it;
* if there's nothing else open, shut down the app;
*
* @type {Object}
*/
var backBtnHandler = {
attachHandler: function(scope) {
this.scope = scope;
},
start: function() {
document.addEventListener("backbutton", handleBackButton, false);
},
stop: function() {
document.removeEventListener("backbutton", handleBackButton, false);
}
};
function handleBackButton(event) {
axe.debug(DEBUG_TAG, 'back button pressed');
// this disarms the default behavior which we NEVER want
event.preventDefault();
event.stopPropagation();
if (backBtnHandler.scope.state.lightbox) {
// closes the lightbox (error msgs, writer, ...)
backBtnHandler.scope.state.lightbox = undefined;
axe.debug(DEBUG_TAG, 'lightbox closed');
backBtnHandler.scope.$apply();
} else if (backBtnHandler.scope.state.read && backBtnHandler.scope.state.read.open) {
// closes the reader
backBtnHandler.scope.state.read.toggle(false);
axe.debug(DEBUG_TAG, 'reader closed');
backBtnHandler.scope.$apply();
} else if (backBtnHandler.scope.state.nav && backBtnHandler.scope.state.nav.open) {
// closes the navigation
backBtnHandler.scope.state.nav.toggle(false);
axe.debug(DEBUG_TAG, 'navigation closed');
backBtnHandler.scope.$apply();
} else {
// exits the app
navigator.app.exitApp();
}
/**
* The back button handler introduces meaningful behavior fo rthe back button:
* if there's an open lightbox, close it;
* if the reader is open in mobile mode, close it;
* if the navigation is open, close it;
* if there's nothing else open, shut down the app;
*
* @type {Object}
*/
var backBtnHandler = {
attachHandler: function(scope) {
this.scope = scope;
},
start: function() {
document.addEventListener("backbutton", handleBackButton, false);
},
stop: function() {
document.removeEventListener("backbutton", handleBackButton, false);
}
};
return backBtnHandler;
});
function handleBackButton(event) {
axe.debug(DEBUG_TAG, 'back button pressed');
// this disarms the default behavior which we NEVER want
event.preventDefault();
event.stopPropagation();
if (backBtnHandler.scope.state.lightbox) {
// closes the lightbox (error msgs, writer, ...)
backBtnHandler.scope.state.lightbox = undefined;
axe.debug(DEBUG_TAG, 'lightbox closed');
backBtnHandler.scope.$apply();
} else if (backBtnHandler.scope.state.read && backBtnHandler.scope.state.read.open) {
// closes the reader
backBtnHandler.scope.state.read.toggle(false);
axe.debug(DEBUG_TAG, 'reader closed');
backBtnHandler.scope.$apply();
} else if (backBtnHandler.scope.state.nav && backBtnHandler.scope.state.nav.open) {
// closes the navigation
backBtnHandler.scope.state.nav.toggle(false);
axe.debug(DEBUG_TAG, 'navigation closed');
backBtnHandler.scope.$apply();
} else {
// exits the app
navigator.app.exitApp();
}
}
module.exports = backBtnHandler;

View File

@ -1,295 +1,298 @@
define(function(require) {
'use strict';
'use strict';
var TCPSocket = require('tcp-socket'),
appConfig = require('js/app-config'),
cfg = appConfig.config,
strings = appConfig.string,
ImapClient = require('imap-client'),
SmtpClient = require('smtpclient');
var TCPSocket = require('tcp-socket'),
appConfig = require('../app-config'),
cfg = appConfig.config,
strings = appConfig.string,
ImapClient = require('imap-client'),
SmtpClient = require('wo-smtpclient');
/**
* The connection doctor can check your connection. In essence, it reconstructs what happens when
* the app goes online in an abbreviated way. You need to configure() the instance with the IMAP/SMTP
* credentials before running check()!
*
* @constructor
*/
var ConnectionDoctor = function() {};
/**
* The connection doctor can check your connection. In essence, it reconstructs what happens when
* the app goes online in an abbreviated way. You need to configure() the instance with the IMAP/SMTP
* credentials before running check()!
*
* @constructor
*/
var ConnectionDoctor = function() {};
//
// Error codes
//
//
// Error codes
//
var OFFLINE = ConnectionDoctor.OFFLINE = 42;
var TLS_WRONG_CERT = ConnectionDoctor.TLS_WRONG_CERT = 43;
var HOST_UNREACHABLE = ConnectionDoctor.HOST_UNREACHABLE = 44;
var HOST_TIMEOUT = ConnectionDoctor.HOST_TIMEOUT = 45;
var AUTH_REJECTED = ConnectionDoctor.AUTH_REJECTED = 46;
var NO_INBOX = ConnectionDoctor.NO_INBOX = 47;
var GENERIC_ERROR = ConnectionDoctor.GENERIC_ERROR = 48;
var OFFLINE = ConnectionDoctor.OFFLINE = 42;
var TLS_WRONG_CERT = ConnectionDoctor.TLS_WRONG_CERT = 43;
var HOST_UNREACHABLE = ConnectionDoctor.HOST_UNREACHABLE = 44;
var HOST_TIMEOUT = ConnectionDoctor.HOST_TIMEOUT = 45;
var AUTH_REJECTED = ConnectionDoctor.AUTH_REJECTED = 46;
var NO_INBOX = ConnectionDoctor.NO_INBOX = 47;
var GENERIC_ERROR = ConnectionDoctor.GENERIC_ERROR = 48;
//
// Public API
//
var WORKER_PATH = cfg.workerPath + '/tcp-socket-tls-worker.min.js';
/**
* Configures the connection doctor
*
* @param {Object} credentials.imap IMAP configuration (host:string, port:number, secure:boolean, ignoreTLS:boolean)
* @param {Object} credentials.smtp SMTP configuration (host:string, port:number, secure:boolean, ignoreTLS:boolean)
* @param {String} credentials.username
* @param {String} credentials.password
*/
ConnectionDoctor.prototype.configure = function(credentials) {
this.credentials = credentials;
//
// Public API
//
// internal members
this._imap = new ImapClient({
host: this.credentials.imap.host,
port: this.credentials.imap.port,
secure: this.credentials.imap.secure,
ignoreTLS: this.credentials.imap.ignoreTLS,
ca: this.credentials.imap.ca,
auth: {
user: this.credentials.username,
pass: this.credentials.password,
xoauth2: this.credentials.xoauth2
}
});
/**
* Configures the connection doctor
*
* @param {Object} credentials.imap IMAP configuration (host:string, port:number, secure:boolean, ignoreTLS:boolean)
* @param {Object} credentials.smtp SMTP configuration (host:string, port:number, secure:boolean, ignoreTLS:boolean)
* @param {String} credentials.username
* @param {String} credentials.password
*/
ConnectionDoctor.prototype.configure = function(credentials) {
this.credentials = credentials;
this._smtp = new SmtpClient(this.credentials.smtp.host, this.credentials.smtp.port, {
useSecureTransport: this.credentials.smtp.secure,
ignoreTLS: this.credentials.smtp.ignoreTLS,
ca: this.credentials.smtp.ca,
auth: {
user: this.credentials.username,
pass: this.credentials.password,
xoauth2: this.credentials.xoauth2
}
});
};
// internal members
this._imap = new ImapClient({
host: this.credentials.imap.host,
port: this.credentials.imap.port,
secure: this.credentials.imap.secure,
ignoreTLS: this.credentials.imap.ignoreTLS,
ca: this.credentials.imap.ca,
tlsWorkerPath: WORKER_PATH,
auth: {
user: this.credentials.username,
pass: this.credentials.password,
xoauth2: this.credentials.xoauth2
}
});
/**
* It conducts the following tests for IMAP and SMTP, respectively:
* 1) Check if browser is online
* 2) Connect to host:port via TCP/TLS
* 3) Login to the server
* 4) Perform some basic commands (e.g. list folders)
* 5) Exposes error codes
*
* @param {Function} callback(error) Invoked when the test suite passed, or with an error object if something went wrong
*/
ConnectionDoctor.prototype.check = function(callback) {
var self = this;
this._smtp = new SmtpClient(this.credentials.smtp.host, this.credentials.smtp.port, {
useSecureTransport: this.credentials.smtp.secure,
ignoreTLS: this.credentials.smtp.ignoreTLS,
ca: this.credentials.smtp.ca,
tlsWorkerPath: WORKER_PATH,
auth: {
user: this.credentials.username,
pass: this.credentials.password,
xoauth2: this.credentials.xoauth2
}
});
};
if (!self.credentials) {
return callback(new Error('You need to configure() the connection doctor first!'));
/**
* It conducts the following tests for IMAP and SMTP, respectively:
* 1) Check if browser is online
* 2) Connect to host:port via TCP/TLS
* 3) Login to the server
* 4) Perform some basic commands (e.g. list folders)
* 5) Exposes error codes
*
* @param {Function} callback(error) Invoked when the test suite passed, or with an error object if something went wrong
*/
ConnectionDoctor.prototype.check = function(callback) {
var self = this;
if (!self.credentials) {
return callback(new Error('You need to configure() the connection doctor first!'));
}
self._checkOnline(function(error) {
if (error) {
return callback(error);
}
self._checkOnline(function(error) {
self._checkReachable(self.credentials.imap, function(error) {
if (error) {
return callback(error);
}
self._checkReachable(self.credentials.imap, function(error) {
self._checkReachable(self.credentials.smtp, function(error) {
if (error) {
return callback(error);
}
self._checkReachable(self.credentials.smtp, function(error) {
self._checkImap(function(error) {
if (error) {
return callback(error);
}
self._checkImap(function(error) {
if (error) {
return callback(error);
}
self._checkSmtp(callback);
});
self._checkSmtp(callback);
});
});
});
};
});
};
//
// Internal API
//
//
// Internal API
//
/**
* Checks if the browser is online
*
* @param {Function} callback(error) Invoked when the test suite passed, or with an error object if browser is offline
*/
ConnectionDoctor.prototype._checkOnline = function(callback) {
if (navigator.onLine) {
callback();
} else {
callback(createError(OFFLINE, strings.connDocOffline));
/**
* Checks if the browser is online
*
* @param {Function} callback(error) Invoked when the test suite passed, or with an error object if browser is offline
*/
ConnectionDoctor.prototype._checkOnline = function(callback) {
if (navigator.onLine) {
callback();
} else {
callback(createError(OFFLINE, strings.connDocOffline));
}
};
/**
* Checks if a host is reachable via TCP
*
* @param {String} options.host
* @param {Number} options.port
* @param {Boolean} options.secure
* @param {Function} callback(error) Invoked when the test suite passed, or with an error object if something went wrong
*/
ConnectionDoctor.prototype._checkReachable = function(options, callback) {
var socket,
error, // remember the error message
timeout, // remember the timeout object
host = options.host + ':' + options.port,
hasTimedOut = false; // prevents multiple callbacks
timeout = setTimeout(function() {
hasTimedOut = true;
callback(createError(HOST_TIMEOUT, strings.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
});
socket.ondata = function() {}; // we don't actually care about the data
// [WO-625] Mozilla forbids extensions to the TCPSocket object,
// throws an exception when assigned unexpected callback functions.
// The exception can be safely ignored since we need the callback
// for the other shims
try {
socket.oncert = function() {
if (options.ca) {
// the certificate we already have is outdated
error = createError(TLS_WRONG_CERT, strings.connDocTlsWrongCert.replace('{0}', host));
}
};
} catch (e) {}
socket.onerror = function(e) {
if (!error) {
error = createError(HOST_UNREACHABLE, strings.connDocHostUnreachable.replace('{0}', host), e.data);
}
};
/**
* Checks if a host is reachable via TCP
*
* @param {String} options.host
* @param {Number} options.port
* @param {Boolean} options.secure
* @param {Function} callback(error) Invoked when the test suite passed, or with an error object if something went wrong
*/
ConnectionDoctor.prototype._checkReachable = function(options, callback) {
var socket,
error, // remember the error message
timeout, // remember the timeout object
host = options.host + ':' + options.port,
hasTimedOut = false; // prevents multiple callbacks
timeout = setTimeout(function() {
hasTimedOut = true;
callback(createError(HOST_TIMEOUT, strings.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
});
socket.ondata = function() {}; // we don't actually care about the data
// [WO-625] Mozilla forbids extensions to the TCPSocket object,
// throws an exception when assigned unexpected callback functions.
// The exception can be safely ignored since we need the callback
// for the other shims
try {
socket.oncert = function() {
if (options.ca) {
// the certificate we already have is outdated
error = createError(TLS_WRONG_CERT, strings.connDocTlsWrongCert.replace('{0}', host));
}
};
} catch (e) {}
socket.onerror = function(e) {
if (!error) {
error = createError(HOST_UNREACHABLE, strings.connDocHostUnreachable.replace('{0}', host), e.data);
}
};
socket.onopen = function() {
socket.close();
};
socket.onclose = function() {
if (!hasTimedOut) {
clearTimeout(timeout);
callback(error);
}
};
socket.onopen = function() {
socket.close();
};
/**
* Checks if an IMAP server is reachable, accepts the credentials, can list folders and has an inbox and logs out.
* Adds the certificate to the IMAP settings if not provided.
*
* @param {Function} callback(error) Invoked when the test suite passed, or with an error object if something went wrong
*/
ConnectionDoctor.prototype._checkImap = function(callback) {
var self = this,
loggedIn = false,
host = self.credentials.imap.host + ':' + self.credentials.imap.port;
socket.onclose = function() {
if (!hasTimedOut) {
clearTimeout(timeout);
callback(error);
}
};
};
/**
* Checks if an IMAP server is reachable, accepts the credentials, can list folders and has an inbox and logs out.
* Adds the certificate to the IMAP settings if not provided.
*
* @param {Function} callback(error) Invoked when the test suite passed, or with an error object if something went wrong
*/
ConnectionDoctor.prototype._checkImap = function(callback) {
var self = this,
loggedIn = false,
host = self.credentials.imap.host + ':' + self.credentials.imap.port;
self._imap.onCert = function(pemEncodedCert) {
if (!self.credentials.imap.ca) {
self.credentials.imap.ca = pemEncodedCert;
self._imap.onCert = function(pemEncodedCert) {
if (!self.credentials.imap.ca) {
self.credentials.imap.ca = pemEncodedCert;
}
};
// login and logout do not use error objects in the callback, but rather invoke
// 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));
} else {
callback(createError(GENERIC_ERROR, strings.connDocGenericError.replace('{0}', host).replace('{1}', error.message), error));
}
};
self._imap.login(function() {
loggedIn = true;
self._imap.listWellKnownFolders(function(error, wellKnownFolders) {
if (error) {
return callback(createError(GENERIC_ERROR, strings.connDocGenericError.replace('{0}', host).replace('{1}', error.message), error));
}
};
// login and logout do not use error objects in the callback, but rather invoke
// 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));
} else {
callback(createError(GENERIC_ERROR, strings.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)));
}
};
self._imap.login(function() {
loggedIn = true;
self._imap.listWellKnownFolders(function(error, wellKnownFolders) {
if (error) {
return callback(createError(GENERIC_ERROR, strings.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)));
}
self._imap.logout(function() {
callback();
});
self._imap.logout(function() {
callback();
});
});
});
};
/**
* Checks if an SMTP server is reachable and accepts the credentials and logs out.
* Adds the certificate to the SMTP settings if not provided.
*
* @param {Function} callback(error) Invoked when the test suite passed, or with an error object if something went wrong
*/
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
self._smtp.oncert = function(pemEncodedCert) {
if (!self.credentials.smtp.ca) {
self.credentials.smtp.ca = pemEncodedCert;
}
};
/**
* Checks if an SMTP server is reachable and accepts the credentials and logs out.
* Adds the certificate to the SMTP settings if not provided.
*
* @param {Function} callback(error) Invoked when the test suite passed, or with an error object if something went wrong
*/
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
self._smtp.oncert = function(pemEncodedCert) {
if (!self.credentials.smtp.ca) {
self.credentials.smtp.ca = pemEncodedCert;
}
};
self._smtp.onerror = function(error) {
if (error) {
errored = true;
callback(createError(AUTH_REJECTED, strings.connDocAuthRejected.replace('{0}', host), error));
}
};
self._smtp.onidle = function() {
self._smtp.quit();
};
self._smtp.onclose = function() {
if (!errored) {
callback();
}
};
self._smtp.connect();
self._smtp.onerror = function(error) {
if (error) {
errored = true;
callback(createError(AUTH_REJECTED, strings.connDocAuthRejected.replace('{0}', host), error));
}
};
self._smtp.onidle = function() {
self._smtp.quit();
};
//
// Helper Functions
//
self._smtp.onclose = function() {
if (!errored) {
callback();
}
};
function createError(code, message, underlyingError) {
var error = new Error(message);
error.code = code;
error.underlyingError = underlyingError;
self._smtp.connect();
};
return error;
}
return ConnectionDoctor;
});
//
// Helper Functions
//
function createError(code, message, underlyingError) {
var error = new Error(message);
error.code = code;
error.underlyingError = underlyingError;
return error;
}
module.exports = ConnectionDoctor;

View File

@ -1,60 +1,58 @@
define(function(require) {
'use strict';
'use strict';
var util = require('js/crypto/util');
var util = require('crypto-lib').util;
var dl = {};
var dl = {};
dl.createDownload = function(options) {
var contentType = options.contentType || 'application/octet-stream';
var filename = options.filename || 'file';
var content = options.content;
var a = document.createElement('a');
var supportsBlob;
dl.createDownload = function(options) {
var contentType = options.contentType || 'application/octet-stream';
var filename = options.filename || 'file';
var content = options.content;
var a = document.createElement('a');
var supportsBlob;
try {
supportsBlob = !!new Blob();
} catch (e) {}
try {
supportsBlob = !!new Blob();
} catch (e) {}
if (typeof a.download !== "undefined" && supportsBlob) {
// ff 30+, chrome 27+ (android: 37+)
document.body.appendChild(a);
a.style = "display: none";
a.href = window.URL.createObjectURL(new Blob([content], {
type: contentType
}));
a.download = filename;
a.click();
setTimeout(function() {
window.URL.revokeObjectURL(a.href);
document.body.removeChild(a);
}, 10); // arbitrary, just get it off the main thread
} else if (window.navigator.msSaveBlob) {
// ie 10+
window.navigator.msSaveBlob(new Blob([content], {
type: contentType
}), filename);
} else if (supportsBlob) {
// safari actually makes no sense:
// - you can't open a new window
// - the file system api is dead
// - download attribute doesn't work
// - behaves randomly (opens a new tab or doesn't, downloads stuff or doesn't, ...)
var url = window.URL.createObjectURL(new Blob([content], {
type: contentType
}));
var newTab = window.open(url, "_blank");
if (!newTab) {
window.location.href = url;
}
} else {
// anything else, where anything at all is better than nothing
if (typeof content !== "string" && content.buffer) {
content = util.arrBuf2BinStr(content.buffer);
}
window.open('data:' + contentType + ';base64,' + btoa(content), "_blank");
if (typeof a.download !== "undefined" && supportsBlob) {
// ff 30+, chrome 27+ (android: 37+)
document.body.appendChild(a);
a.style = "display: none";
a.href = window.URL.createObjectURL(new Blob([content], {
type: contentType
}));
a.download = filename;
a.click();
setTimeout(function() {
window.URL.revokeObjectURL(a.href);
document.body.removeChild(a);
}, 10); // arbitrary, just get it off the main thread
} else if (window.navigator.msSaveBlob) {
// ie 10+
window.navigator.msSaveBlob(new Blob([content], {
type: contentType
}), filename);
} else if (supportsBlob) {
// safari actually makes no sense:
// - you can't open a new window
// - the file system api is dead
// - download attribute doesn't work
// - behaves randomly (opens a new tab or doesn't, downloads stuff or doesn't, ...)
var url = window.URL.createObjectURL(new Blob([content], {
type: contentType
}));
var newTab = window.open(url, "_blank");
if (!newTab) {
window.location.href = url;
}
};
} else {
// anything else, where anything at all is better than nothing
if (typeof content !== "string" && content.buffer) {
content = util.arrBuf2BinStr(content.buffer);
}
window.open('data:' + contentType + ';base64,' + btoa(content), "_blank");
}
};
return dl;
});
module.exports = dl;

View File

@ -1,35 +1,33 @@
define(function(require) {
'use strict';
'use strict';
var axe = require('axe');
var axe = require('axe-logger');
var er = {};
er.attachHandler = function(scope) {
scope.onError = function(options) {
if (!options) {
scope.$apply();
return;
}
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) : ''));
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();
}
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();
}
};
};
return er;
});
module.exports = er;

View File

@ -1,60 +1,58 @@
define(function(require) {
'use strict';
'use strict';
var cfg = require('js/app-config').config;
var cfg = require('../app-config').config;
var self = {};
var notif = {};
if (window.Notification) {
self.hasPermission = Notification.permission === "granted";
if (window.Notification) {
notif.hasPermission = Notification.permission === "granted";
}
/**
* Creates a notification. Requests permission if not already granted
*
* @param {String} options.title The notification title
* @param {String} options.message The notification message
* @param {Number} options.timeout (optional) Timeout when the notification is closed in milliseconds
* @param {Function} options.onClick (optional) callback when the notification is clicked
* @returns {Notification} A notification instance
*/
notif.create = function(options) {
options.onClick = options.onClick || function() {};
if (!window.Notification) {
return;
}
/**
* Creates a notification. Requests permission if not already granted
*
* @param {String} options.title The notification title
* @param {String} options.message The notification message
* @param {Number} options.timeout (optional) Timeout when the notification is closed in milliseconds
* @param {Function} options.onClick (optional) callback when the notification is clicked
* @returns {Notification} A notification instance
*/
self.create = function(options) {
options.onClick = options.onClick || function() {};
if (!window.Notification) {
return;
}
if (!self.hasPermission) {
// don't wait until callback returns
Notification.requestPermission(function(permission) {
if (permission === "granted") {
self.hasPermission = true;
}
});
}
var notification = new Notification(options.title, {
body: options.message,
icon: cfg.iconPath
if (!notif.hasPermission) {
// don't wait until callback returns
Notification.requestPermission(function(permission) {
if (permission === "granted") {
notif.hasPermission = true;
}
});
notification.onclick = function() {
window.focus();
options.onClick();
};
}
if (options.timeout > 0) {
setTimeout(function() {
notification.close();
}, options.timeout);
}
return notification;
var notification = new Notification(options.title, {
body: options.message,
icon: cfg.iconPath
});
notification.onclick = function() {
window.focus();
options.onClick();
};
self.close = function(notification) {
notification.close();
};
if (options.timeout > 0) {
setTimeout(function() {
notification.close();
}, options.timeout);
}
return self;
});
return notification;
};
notif.close = function(notification) {
notification.close();
};
module.exports = notif;

View File

@ -1,101 +1,99 @@
define(function() {
'use strict';
'use strict';
var OAuth = function(googleApi) {
this._googleApi = googleApi;
var OAuth = function(googleApi) {
this._googleApi = googleApi;
};
/**
* Check if chrome.identity api is supported
* @return {Boolean} If is supported
*/
OAuth.prototype.isSupported = function() {
return !!(window.chrome && chrome.identity);
};
/**
* Request an OAuth token from chrome for gmail users
* @param {String} emailAddress The user's email address (optional)
*/
OAuth.prototype.getOAuthToken = function(emailAddress, callback) {
var idOptions = {
interactive: true
};
/**
* Check if chrome.identity api is supported
* @return {Boolean} If is supported
*/
OAuth.prototype.isSupported = function() {
return !!(window.chrome && chrome.identity);
};
/**
* Request an OAuth token from chrome for gmail users
* @param {String} emailAddress The user's email address (optional)
*/
OAuth.prototype.getOAuthToken = function(emailAddress, callback) {
var idOptions = {
interactive: true
};
// check which runtime the app is running under
chrome.runtime.getPlatformInfo(function(platformInfo) {
if (chrome.runtime.lastError || !platformInfo) {
callback(new Error('Error getting chrome platform info!'));
return;
}
if (emailAddress && platformInfo.os.indexOf('android') !== -1) {
// set accountHint so that native Android account picker does not show up each time
idOptions.accountHint = emailAddress;
}
// get OAuth Token from chrome
chrome.identity.getAuthToken(idOptions, function(token) {
if (chrome.runtime.lastError || !token) {
callback({
errMsg: 'Error fetching an OAuth token for the user!'
});
return;
}
callback(null, token);
});
});
};
/**
* Remove an old OAuth token and get a new one.
* @param {String} options.oldToken The old token to be removed
* @param {String} options.emailAddress The user's email address (optional)
*/
OAuth.prototype.refreshToken = function(options, callback) {
var self = this;
if (!options.oldToken) {
callback(new Error('oldToken option not set!'));
// check which runtime the app is running under
chrome.runtime.getPlatformInfo(function(platformInfo) {
if (chrome.runtime.lastError || !platformInfo) {
callback(new Error('Error getting chrome platform info!'));
return;
}
// remove cached token
chrome.identity.removeCachedAuthToken({
token: options.oldToken
}, function() {
// get a new token
self.getOAuthToken(options.emailAddress, callback);
});
};
/**
* Get email address from google api
* @param {String} token The oauth token
*/
OAuth.prototype.queryEmailAddress = function(token, callback) {
if (!token) {
callback({
errMsg: 'Invalid OAuth token!'
});
return;
if (emailAddress && platformInfo.os.indexOf('android') !== -1) {
// set accountHint so that native Android account picker does not show up each time
idOptions.accountHint = emailAddress;
}
// fetch gmail user's email address from the Google Authorization Server
this._googleApi.get({
uri: '/oauth2/v3/userinfo?access_token=' + token
}, function(err, info) {
if (err || !info || !info.email) {
// get OAuth Token from chrome
chrome.identity.getAuthToken(idOptions, function(token) {
if (chrome.runtime.lastError || !token) {
callback({
errMsg: 'Error looking up email address on google api!'
errMsg: 'Error fetching an OAuth token for the user!'
});
return;
}
callback(null, info.email);
callback(null, token);
});
};
});
};
return OAuth;
});
/**
* Remove an old OAuth token and get a new one.
* @param {String} options.oldToken The old token to be removed
* @param {String} options.emailAddress The user's email address (optional)
*/
OAuth.prototype.refreshToken = function(options, callback) {
var self = this;
if (!options.oldToken) {
callback(new Error('oldToken option not set!'));
return;
}
// remove cached token
chrome.identity.removeCachedAuthToken({
token: options.oldToken
}, function() {
// get a new token
self.getOAuthToken(options.emailAddress, callback);
});
};
/**
* Get email address from google api
* @param {String} token The oauth token
*/
OAuth.prototype.queryEmailAddress = function(token, callback) {
if (!token) {
callback({
errMsg: 'Invalid OAuth token!'
});
return;
}
// fetch gmail user's email address from the Google Authorization Server
this._googleApi.get({
uri: '/oauth2/v3/userinfo?access_token=' + token
}, function(err, info) {
if (err || !info || !info.email) {
callback({
errMsg: 'Error looking up email address on google api!'
});
return;
}
callback(null, info.email);
});
};
module.exports = OAuth;

View File

@ -1,132 +1,130 @@
define(function(require) {
'use strict';
'use strict';
var axe = require('axe'),
cfg = require('js/app-config').config,
updateV1 = require('js/util/update/update-v1'),
updateV2 = require('js/util/update/update-v2'),
updateV3 = require('js/util/update/update-v3'),
updateV4 = require('js/util/update/update-v4'),
updateV5 = require('js/util/update/update-v5');
var axe = require('axe-logger'),
cfg = require('../../app-config').config,
updateV1 = require('./update-v1'),
updateV2 = require('./update-v2'),
updateV3 = require('./update-v3'),
updateV4 = require('./update-v4'),
updateV5 = require('./update-v5');
/**
* Handles database migration
*/
var UpdateHandler = function(appConfigStorage, userStorage, auth) {
this._appConfigStorage = appConfigStorage;
this._userStorage = userStorage;
this._updateScripts = [updateV1, updateV2, updateV3, updateV4, updateV5];
this._auth = auth;
/**
* Handles database migration
*/
var UpdateHandler = function(appConfigStorage, userStorage, auth) {
this._appConfigStorage = appConfigStorage;
this._userStorage = userStorage;
this._updateScripts = [updateV1, updateV2, updateV3, updateV4, updateV5];
this._auth = auth;
};
/**
* Executes all the necessary updates
* @param {Function} callback(error) Invoked when all the database updates were executed, or if an error occurred
*/
UpdateHandler.prototype.update = function(callback) {
var self = this,
currentVersion = 0,
targetVersion = cfg.dbVersion,
versionDbType = 'dbVersion';
self._appConfigStorage.listItems(versionDbType, 0, null, function(err, items) {
if (err) {
callback(err);
return;
}
// parse the database version number
if (items && items.length > 0) {
currentVersion = parseInt(items[0], 10);
}
self._applyUpdate({
currentVersion: currentVersion,
targetVersion: targetVersion
}, callback);
});
};
/**
* Schedules necessary updates and executes thom in order
*/
UpdateHandler.prototype._applyUpdate = function(options, callback) {
var self = this,
scriptOptions,
queue = [];
if (options.currentVersion >= options.targetVersion) {
// the current database version is up to date
callback();
return;
}
scriptOptions = {
appConfigStorage: self._appConfigStorage,
userStorage: self._userStorage,
auth: self._auth
};
/**
* Executes all the necessary updates
* @param {Function} callback(error) Invoked when all the database updates were executed, or if an error occurred
*/
UpdateHandler.prototype.update = function(callback) {
var self = this,
currentVersion = 0,
targetVersion = cfg.dbVersion,
versionDbType = 'dbVersion';
// add all the necessary database updates to the queue
for (var i = options.currentVersion; i < options.targetVersion; i++) {
queue.push(self._updateScripts[i]);
}
self._appConfigStorage.listItems(versionDbType, 0, null, function(err, items) {
if (err) {
callback(err);
return;
}
// takes the next update from the queue and executes it
function executeNextUpdate(err) {
if (err) {
callback(err);
return;
}
// parse the database version number
if (items && items.length > 0) {
currentVersion = parseInt(items[0], 10);
}
self._applyUpdate({
currentVersion: currentVersion,
targetVersion: targetVersion
}, callback);
});
};
/**
* Schedules necessary updates and executes thom in order
*/
UpdateHandler.prototype._applyUpdate = function(options, callback) {
var self = this,
scriptOptions,
queue = [];
if (options.currentVersion >= options.targetVersion) {
// the current database version is up to date
if (queue.length < 1) {
// we're done
callback();
return;
}
scriptOptions = {
appConfigStorage: self._appConfigStorage,
userStorage: self._userStorage,
auth: self._auth
};
// process next update
var script = queue.shift();
script(scriptOptions, executeNextUpdate);
}
// add all the necessary database updates to the queue
for (var i = options.currentVersion; i < options.targetVersion; i++) {
queue.push(self._updateScripts[i]);
}
executeNextUpdate();
};
// takes the next update from the queue and executes it
function executeNextUpdate(err) {
if (err) {
callback(err);
return;
}
if (queue.length < 1) {
// we're done
callback();
return;
}
// process next update
var script = queue.shift();
script(scriptOptions, executeNextUpdate);
}
executeNextUpdate();
};
/**
* Check application version and update correspondingly
*/
UpdateHandler.prototype.checkForUpdate = function(dialog) {
// 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({
title: 'Update available',
message: 'A new version ' + details.version + ' of the app is available. Restart the app to update?',
positiveBtnStr: 'Restart',
negativeBtnStr: 'Not now',
showNegativeBtn: true,
callback: function(agree) {
if (agree) {
chrome.runtime.reload();
}
/**
* Check application version and update correspondingly
*/
UpdateHandler.prototype.checkForUpdate = function(dialog) {
// 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({
title: 'Update available',
message: 'A new version ' + details.version + ' of the app is available. Restart the app to update?',
positiveBtnStr: 'Restart',
negativeBtnStr: 'Not now',
showNegativeBtn: true,
callback: function(agree) {
if (agree) {
chrome.runtime.reload();
}
});
});
chrome.runtime.requestUpdateCheck(function(status) {
if (status === "update_found") {
axe.debug("Update pending...");
} else if (status === "no_update") {
axe.debug("No update found.");
} else if (status === "throttled") {
axe.debug("Checking updates too frequently.");
}
});
}
};
});
chrome.runtime.requestUpdateCheck(function(status) {
if (status === "update_found") {
axe.debug("Update pending...");
} else if (status === "no_update") {
axe.debug("No update found.");
} else if (status === "throttled") {
axe.debug("Checking updates too frequently.");
}
});
}
};
return UpdateHandler;
});
module.exports = UpdateHandler;

View File

@ -1,29 +1,27 @@
define(function() {
'use strict';
'use strict';
/**
* Update handler for transition database version 0 -> 1
*
* In database version 1, the stored email objects have to be purged, otherwise
* every non-prefixed mail in the IMAP folders would be nuked due to the implementation
* of the delta sync.
*/
function updateV1(options, callback) {
var emailDbType = 'email_',
versionDbType = 'dbVersion',
postUpdateDbVersion = 1;
/**
* Update handler for transition database version 0 -> 1
*
* In database version 1, the stored email objects have to be purged, otherwise
* every non-prefixed mail in the IMAP folders would be nuked due to the implementation
* of the delta sync.
*/
function updateV1(options, callback) {
var emailDbType = 'email_',
versionDbType = 'dbVersion',
postUpdateDbVersion = 1;
// remove the emails
options.userStorage.removeList(emailDbType, function(err) {
if (err) {
callback(err);
return;
}
// remove the emails
options.userStorage.removeList(emailDbType, function(err) {
if (err) {
callback(err);
return;
}
// update the database version to postUpdateDbVersion
options.appConfigStorage.storeList([postUpdateDbVersion], versionDbType, callback);
});
}
// update the database version to postUpdateDbVersion
options.appConfigStorage.storeList([postUpdateDbVersion], versionDbType, callback);
});
}
return updateV1;
});
module.exports = updateV1;

View File

@ -1,28 +1,26 @@
define(function() {
'use strict';
'use strict';
/**
* Update handler for transition database version 1 -> 2
*
* In database version 2, the stored email objects have to be purged, because the
* new data model stores information about the email structure in the property 'bodyParts'.
*/
function updateV2(options, callback) {
var emailDbType = 'email_',
versionDbType = 'dbVersion',
postUpdateDbVersion = 2;
/**
* Update handler for transition database version 1 -> 2
*
* In database version 2, the stored email objects have to be purged, because the
* new data model stores information about the email structure in the property 'bodyParts'.
*/
function updateV2(options, callback) {
var emailDbType = 'email_',
versionDbType = 'dbVersion',
postUpdateDbVersion = 2;
// remove the emails
options.userStorage.removeList(emailDbType, function(err) {
if (err) {
callback(err);
return;
}
// remove the emails
options.userStorage.removeList(emailDbType, function(err) {
if (err) {
callback(err);
return;
}
// update the database version to postUpdateDbVersion
options.appConfigStorage.storeList([postUpdateDbVersion], versionDbType, callback);
});
}
// update the database version to postUpdateDbVersion
options.appConfigStorage.storeList([postUpdateDbVersion], versionDbType, callback);
});
}
return updateV2;
});
module.exports = updateV2;

View File

@ -1,28 +1,26 @@
define(function() {
'use strict';
'use strict';
/**
* Update handler for transition database version 2 -> 3
*
* In database version 3, we introduced new flags to the messages, also
* the outbox uses artificial uids
*/
function update(options, callback) {
var emailDbType = 'email_',
versionDbType = 'dbVersion',
postUpdateDbVersion = 3;
/**
* Update handler for transition database version 2 -> 3
*
* In database version 3, we introduced new flags to the messages, also
* the outbox uses artificial uids
*/
function update(options, callback) {
var emailDbType = 'email_',
versionDbType = 'dbVersion',
postUpdateDbVersion = 3;
// remove the emails
options.userStorage.removeList(emailDbType, function(err) {
if (err) {
callback(err);
return;
}
// remove the emails
options.userStorage.removeList(emailDbType, function(err) {
if (err) {
callback(err);
return;
}
// update the database version to postUpdateDbVersion
options.appConfigStorage.storeList([postUpdateDbVersion], versionDbType, callback);
});
}
// update the database version to postUpdateDbVersion
options.appConfigStorage.storeList([postUpdateDbVersion], versionDbType, callback);
});
}
return update;
});
module.exports = update;

View File

@ -1,91 +1,89 @@
define(function(require) {
'use strict';
'use strict';
var config = require('js/app-config').config;
var config = require('../../app-config').config;
/**
* Update handler for transition database version 3 -> 4
*
* In database version 4, we need to add a "provider" flag to the
* indexeddb. only gmail was allowed as a mail service provider before,
* so let's add this...
*/
function update(options, callback) {
var VERSION_DB_TYPE = 'dbVersion',
EMAIL_ADDR_DB_KEY = 'emailaddress',
USERNAME_DB_KEY = 'username',
PROVIDER_DB_KEY = 'provider',
IMAP_DB_KEY = 'imap',
SMTP_DB_KEY = 'smtp',
REALNAME_DB_KEY = 'realname',
POST_UPDATE_DB_VERSION = 4;
/**
* Update handler for transition database version 3 -> 4
*
* In database version 4, we need to add a "provider" flag to the
* indexeddb. only gmail was allowed as a mail service provider before,
* so let's add this...
*/
function update(options, callback) {
var VERSION_DB_TYPE = 'dbVersion',
EMAIL_ADDR_DB_KEY = 'emailaddress',
USERNAME_DB_KEY = 'username',
PROVIDER_DB_KEY = 'provider',
IMAP_DB_KEY = 'imap',
SMTP_DB_KEY = 'smtp',
REALNAME_DB_KEY = 'realname',
POST_UPDATE_DB_VERSION = 4;
var imap = config.gmail.imap,
smtp = config.gmail.smtp;
var imap = config.gmail.imap,
smtp = config.gmail.smtp;
// load the email address (if existing)
loadFromDB(EMAIL_ADDR_DB_KEY, function(err, emailAddress) {
// load the email address (if existing)
loadFromDB(EMAIL_ADDR_DB_KEY, function(err, emailAddress) {
if (err) {
return callback(err);
}
// load the provider (if existing)
loadFromDB(PROVIDER_DB_KEY, function(err, provider) {
if (err) {
return callback(err);
}
// load the provider (if existing)
loadFromDB(PROVIDER_DB_KEY, function(err, provider) {
// if there is an email address without a provider, we need to add the missing provider entry
// for any other situation, we're good.
if (!(emailAddress && !provider)) {
// update the database version to POST_UPDATE_DB_VERSION
return options.appConfigStorage.storeList([POST_UPDATE_DB_VERSION], VERSION_DB_TYPE, callback);
}
// add the missing provider key
options.appConfigStorage.storeList(['gmail'], PROVIDER_DB_KEY, function(err) {
if (err) {
return callback(err);
}
// if there is an email address without a provider, we need to add the missing provider entry
// for any other situation, we're good.
if (!(emailAddress && !provider)) {
// update the database version to POST_UPDATE_DB_VERSION
return options.appConfigStorage.storeList([POST_UPDATE_DB_VERSION], VERSION_DB_TYPE, callback);
}
// add the missing provider key
options.appConfigStorage.storeList(['gmail'], PROVIDER_DB_KEY, function(err) {
// add the missing user name key
options.appConfigStorage.storeList([emailAddress], USERNAME_DB_KEY, function(err) {
if (err) {
return callback(err);
}
// add the missing user name key
options.appConfigStorage.storeList([emailAddress], USERNAME_DB_KEY, function(err) {
// add the missing imap host info key
options.appConfigStorage.storeList([imap], IMAP_DB_KEY, function(err) {
if (err) {
return callback(err);
}
// add the missing imap host info key
options.appConfigStorage.storeList([imap], IMAP_DB_KEY, function(err) {
// add the missing empty real name
options.appConfigStorage.storeList([''], REALNAME_DB_KEY, function(err) {
if (err) {
return callback(err);
}
// add the missing empty real name
options.appConfigStorage.storeList([''], REALNAME_DB_KEY, function(err) {
// add the missing smtp host info key
options.appConfigStorage.storeList([smtp], SMTP_DB_KEY, function(err) {
if (err) {
return callback(err);
}
// add the missing smtp host info key
options.appConfigStorage.storeList([smtp], SMTP_DB_KEY, function(err) {
// reload the credentials
options.auth.initialized = false;
options.auth._loadCredentials(function(err) {
if (err) {
return callback(err);
}
// reload the credentials
options.auth.initialized = false;
options.auth._loadCredentials(function(err) {
if (err) {
return callback(err);
}
// update the database version to POST_UPDATE_DB_VERSION
options.appConfigStorage.storeList([POST_UPDATE_DB_VERSION], VERSION_DB_TYPE, callback);
});
// update the database version to POST_UPDATE_DB_VERSION
options.appConfigStorage.storeList([POST_UPDATE_DB_VERSION], VERSION_DB_TYPE, callback);
});
});
});
@ -93,13 +91,13 @@ define(function(require) {
});
});
});
});
function loadFromDB(key, callback) {
options.appConfigStorage.listItems(key, 0, null, function(err, cachedItems) {
callback(err, (!err && cachedItems && cachedItems[0]));
});
}
function loadFromDB(key, callback) {
options.appConfigStorage.listItems(key, 0, null, function(err, cachedItems) {
callback(err, (!err && cachedItems && cachedItems[0]));
});
}
}
return update;
});
module.exports = update;

View File

@ -1,56 +1,54 @@
define(function() {
'use strict';
'use strict';
var FOLDER_TYPE_INBOX = 'Inbox';
var FOLDER_TYPE_SENT = 'Sent';
var FOLDER_TYPE_DRAFTS = 'Drafts';
var FOLDER_TYPE_TRASH = 'Trash';
var FOLDER_TYPE_INBOX = 'Inbox';
var FOLDER_TYPE_SENT = 'Sent';
var FOLDER_TYPE_DRAFTS = 'Drafts';
var FOLDER_TYPE_TRASH = 'Trash';
var FOLDER_DB_TYPE = 'folders';
var VERSION_DB_TYPE = 'dbVersion';
var FOLDER_DB_TYPE = 'folders';
var VERSION_DB_TYPE = 'dbVersion';
var POST_UPDATE_DB_VERSION = 5;
var POST_UPDATE_DB_VERSION = 5;
/**
* Update handler for transition database version 4 -> 5
*
* Due to an overlooked issue, there may be multiple folders, e.g. for sent mails.
* This removes the "duplicate" folders.
*/
function update(options, callback) {
/**
* Update handler for transition database version 4 -> 5
*
* Due to an overlooked issue, there may be multiple folders, e.g. for sent mails.
* This removes the "duplicate" folders.
*/
function update(options, callback) {
// remove the emails
options.userStorage.listItems(FOLDER_DB_TYPE, 0, null, function(err, stored) {
// remove the emails
options.userStorage.listItems(FOLDER_DB_TYPE, 0, null, function(err, stored) {
if (err) {
return callback(err);
}
var folders = stored[0] || [];
[FOLDER_TYPE_INBOX, FOLDER_TYPE_SENT, FOLDER_TYPE_DRAFTS, FOLDER_TYPE_TRASH].forEach(function(mbxType) {
var foldersForType = folders.filter(function(mbx) {
return mbx.type === mbxType;
});
if (foldersForType.length <= 1) {
return; // nothing to do here
}
// remove duplicate folders
for (var i = 1; i < foldersForType.length; i++) {
folders.splice(folders.indexOf(foldersForType[i]), 1);
}
});
options.userStorage.storeList([folders], FOLDER_DB_TYPE, function(err) {
if (err) {
return callback(err);
}
var folders = stored[0] || [];
[FOLDER_TYPE_INBOX, FOLDER_TYPE_SENT, FOLDER_TYPE_DRAFTS, FOLDER_TYPE_TRASH].forEach(function(mbxType) {
var foldersForType = folders.filter(function(mbx) {
return mbx.type === mbxType;
});
if (foldersForType.length <= 1) {
return; // nothing to do here
}
// remove duplicate folders
for (var i = 1; i < foldersForType.length; i++) {
folders.splice(folders.indexOf(foldersForType[i]), 1);
}
});
options.userStorage.storeList([folders], FOLDER_DB_TYPE, function(err) {
if (err) {
return callback(err);
}
// update the database version to POST_UPDATE_DB_VERSION
options.appConfigStorage.storeList([POST_UPDATE_DB_VERSION], VERSION_DB_TYPE, callback);
});
// update the database version to POST_UPDATE_DB_VERSION
options.appConfigStorage.storeList([POST_UPDATE_DB_VERSION], VERSION_DB_TYPE, callback);
});
}
});
}
return update;
});
module.exports = update;

File diff suppressed because one or more lines are too long

View File

@ -1,245 +0,0 @@
// uuid.js
//
// (c) 2010-2012 Robert Kieffer
// MIT License
// https://github.com/broofa/node-uuid
(function() {
var _global = this;
// Unique ID creation requires a high quality random # generator. We feature
// detect to determine the best RNG source, normalizing to a function that
// returns 128-bits of randomness, since that's what's usually required
var _rng;
// Node.js crypto-based RNG - http://nodejs.org/docs/v0.6.2/api/crypto.html
//
// Moderately fast, high quality
if (typeof(require) == 'function') {
try {
var _rb = require('crypto').randomBytes;
_rng = _rb && function() {return _rb(16);};
} catch(e) {}
}
if (!_rng && _global.crypto && crypto.getRandomValues) {
// WHATWG crypto-based RNG - http://wiki.whatwg.org/wiki/Crypto
//
// Moderately fast, high quality
var _rnds8 = new Uint8Array(16);
_rng = function whatwgRNG() {
crypto.getRandomValues(_rnds8);
return _rnds8;
};
}
if (!_rng) {
// Math.random()-based (RNG)
//
// If all else fails, use Math.random(). It's fast, but is of unspecified
// quality.
var _rnds = new Array(16);
_rng = function() {
for (var i = 0, r; i < 16; i++) {
if ((i & 0x03) === 0) r = Math.random() * 0x100000000;
_rnds[i] = r >>> ((i & 0x03) << 3) & 0xff;
}
return _rnds;
};
}
// Buffer class to use
var BufferClass = typeof(Buffer) == 'function' ? Buffer : Array;
// Maps for number <-> hex string conversion
var _byteToHex = [];
var _hexToByte = {};
for (var i = 0; i < 256; i++) {
_byteToHex[i] = (i + 0x100).toString(16).substr(1);
_hexToByte[_byteToHex[i]] = i;
}
// **`parse()` - Parse a UUID into it's component bytes**
function parse(s, buf, offset) {
var i = (buf && offset) || 0, ii = 0;
buf = buf || [];
s.toLowerCase().replace(/[0-9a-f]{2}/g, function(oct) {
if (ii < 16) { // Don't overflow!
buf[i + ii++] = _hexToByte[oct];
}
});
// Zero out remaining bytes if string was short
while (ii < 16) {
buf[i + ii++] = 0;
}
return buf;
}
// **`unparse()` - Convert UUID byte array (ala parse()) into a string**
function unparse(buf, offset) {
var i = offset || 0, bth = _byteToHex;
return bth[buf[i++]] + bth[buf[i++]] +
bth[buf[i++]] + bth[buf[i++]] + '-' +
bth[buf[i++]] + bth[buf[i++]] + '-' +
bth[buf[i++]] + bth[buf[i++]] + '-' +
bth[buf[i++]] + bth[buf[i++]] + '-' +
bth[buf[i++]] + bth[buf[i++]] +
bth[buf[i++]] + bth[buf[i++]] +
bth[buf[i++]] + bth[buf[i++]];
}
// **`v1()` - Generate time-based UUID**
//
// Inspired by https://github.com/LiosK/UUID.js
// and http://docs.python.org/library/uuid.html
// random #'s we need to init node and clockseq
var _seedBytes = _rng();
// Per 4.5, create and 48-bit node id, (47 random bits + multicast bit = 1)
var _nodeId = [
_seedBytes[0] | 0x01,
_seedBytes[1], _seedBytes[2], _seedBytes[3], _seedBytes[4], _seedBytes[5]
];
// Per 4.2.2, randomize (14 bit) clockseq
var _clockseq = (_seedBytes[6] << 8 | _seedBytes[7]) & 0x3fff;
// Previous uuid creation time
var _lastMSecs = 0, _lastNSecs = 0;
// See https://github.com/broofa/node-uuid for API details
function v1(options, buf, offset) {
var i = buf && offset || 0;
var b = buf || [];
options = options || {};
var clockseq = options.clockseq != null ? options.clockseq : _clockseq;
// UUID timestamps are 100 nano-second units since the Gregorian epoch,
// (1582-10-15 00:00). JSNumbers aren't precise enough for this, so
// time is handled internally as 'msecs' (integer milliseconds) and 'nsecs'
// (100-nanoseconds offset from msecs) since unix epoch, 1970-01-01 00:00.
var msecs = options.msecs != null ? options.msecs : new Date().getTime();
// Per 4.2.1.2, use count of uuid's generated during the current clock
// cycle to simulate higher resolution clock
var nsecs = options.nsecs != null ? options.nsecs : _lastNSecs + 1;
// Time since last uuid creation (in msecs)
var dt = (msecs - _lastMSecs) + (nsecs - _lastNSecs)/10000;
// Per 4.2.1.2, Bump clockseq on clock regression
if (dt < 0 && options.clockseq == null) {
clockseq = clockseq + 1 & 0x3fff;
}
// Reset nsecs if clock regresses (new clockseq) or we've moved onto a new
// time interval
if ((dt < 0 || msecs > _lastMSecs) && options.nsecs == null) {
nsecs = 0;
}
// Per 4.2.1.2 Throw error if too many uuids are requested
if (nsecs >= 10000) {
throw new Error('uuid.v1(): Can\'t create more than 10M uuids/sec');
}
_lastMSecs = msecs;
_lastNSecs = nsecs;
_clockseq = clockseq;
// Per 4.1.4 - Convert from unix epoch to Gregorian epoch
msecs += 12219292800000;
// `time_low`
var tl = ((msecs & 0xfffffff) * 10000 + nsecs) % 0x100000000;
b[i++] = tl >>> 24 & 0xff;
b[i++] = tl >>> 16 & 0xff;
b[i++] = tl >>> 8 & 0xff;
b[i++] = tl & 0xff;
// `time_mid`
var tmh = (msecs / 0x100000000 * 10000) & 0xfffffff;
b[i++] = tmh >>> 8 & 0xff;
b[i++] = tmh & 0xff;
// `time_high_and_version`
b[i++] = tmh >>> 24 & 0xf | 0x10; // include version
b[i++] = tmh >>> 16 & 0xff;
// `clock_seq_hi_and_reserved` (Per 4.2.2 - include variant)
b[i++] = clockseq >>> 8 | 0x80;
// `clock_seq_low`
b[i++] = clockseq & 0xff;
// `node`
var node = options.node || _nodeId;
for (var n = 0; n < 6; n++) {
b[i + n] = node[n];
}
return buf ? buf : unparse(b);
}
// **`v4()` - Generate random UUID**
// See https://github.com/broofa/node-uuid for API details
function v4(options, buf, offset) {
// Deprecated - 'format' argument, as supported in v1.2
var i = buf && offset || 0;
if (typeof(options) == 'string') {
buf = options == 'binary' ? new BufferClass(16) : null;
options = null;
}
options = options || {};
var rnds = options.random || (options.rng || _rng)();
// Per 4.4, set bits for version and `clock_seq_hi_and_reserved`
rnds[6] = (rnds[6] & 0x0f) | 0x40;
rnds[8] = (rnds[8] & 0x3f) | 0x80;
// Copy bytes to buffer, if provided
if (buf) {
for (var ii = 0; ii < 16; ii++) {
buf[i + ii] = rnds[ii];
}
}
return buf || unparse(rnds);
}
// Export public API
var uuid = v4;
uuid.v1 = v1;
uuid.v4 = v4;
uuid.parse = parse;
uuid.unparse = unparse;
uuid.BufferClass = BufferClass;
if (_global.define && define.amd) {
// Publish as AMD module
define(function() {return uuid;});
} else if (typeof(module) != 'undefined' && module.exports) {
// Publish as node.js module
module.exports = uuid;
} else {
// Publish as global (in browsers)
var _previousRoot = _global.uuid;
// **`noConflict()` - (browser only) to reset global 'uuid' var**
uuid.noConflict = function() {
_global.uuid = _previousRoot;
return uuid;
};
_global.uuid = uuid;
}
}());

View File

@ -1,10 +0,0 @@
{
"name": "Whiteout Mail",
"version": "0.0.1",
"main": "index.html",
"window": {
"toolbar": false,
"width": 1024,
"height": 768
}
}

View File

@ -1,68 +0,0 @@
(function() {
'use strict';
requirejs.config({
nodeRequire: (typeof module !== 'undefined' && module.exports) ? require : undefined,
baseUrl: 'lib',
paths: {
js: '../js',
test: '../../test',
jquery: 'jquery.min',
underscore: 'underscore/underscore-min',
lawnchair: 'lawnchair/lawnchair-git',
lawnchairSQL: 'lawnchair/lawnchair-adapter-webkit-sqlite-git',
lawnchairIDB: 'lawnchair/lawnchair-adapter-indexed-db-git',
angular: 'angular/angular.min',
angularRoute: 'angular/angular-route.min',
angularAnimate: 'angular/angular-animate.min',
ngInfiniteScroll: 'ng-infinite-scroll.min',
ngTagsInput: 'ngtagsinput/ng-tags-input.min',
uuid: 'uuid/uuid',
forge: 'forge/forge.min',
punycode: 'punycode.min',
openpgp: 'openpgp/openpgp',
fastclick: 'fastclick/fastclick'
},
shim: {
forge: {
exports: 'forge'
},
jquery: {
exports: '$'
},
angular: {
exports: 'angular',
deps: ['jquery']
},
angularRoute: {
exports: 'angular',
deps: ['angular']
},
angularAnimate: {
exports: 'angular',
deps: ['angular']
},
ngInfiniteScroll: {
exports: 'angular',
deps: ['jquery', 'angular']
},
ngTagsInput: {
exports: 'angular',
deps: ['angular']
},
lawnchair: {
exports: 'Lawnchair'
},
lawnchairSQL: {
deps: ['lawnchair']
},
lawnchairIDB: {
deps: ['lawnchair', 'lawnchairSQL']
},
underscore: {
exports: '_'
}
}
});
}());

View File

@ -5,8 +5,7 @@
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="stylesheet" media="all" href="../css/read-sandbox.min.css" type="text/css">
<script src="../lib/purify.js"></script>
<script src="../js/controller/read-sandbox.js"></script>
<script src="../js/read-sandbox.min.js"></script>
</head>
<body></body>

View File

@ -1,10 +0,0 @@
'use strict';
chrome.app.runtime.onLaunched.addListener(function() {
chrome.app.window.create('integration/index.html', {
'bounds': {
'width': 1024,
'height': 768
}
});
});

File diff suppressed because one or more lines are too long

View File

@ -2,7 +2,7 @@
<html style="overflow-y: auto">
<head>
<meta charset="utf-8">
<title>JavaScript Unit Tests</title>
<title>JavaScript Integration Tests</title>
<link rel="stylesheet" href="../lib/mocha.css" />
</head>
@ -10,10 +10,24 @@
<div id="mocha"></div>
<script src="../lib/chai.js"></script>
<script src="../lib/sinon.js"></script>
<script src="../lib/mocha.js"></script>
<script src="../lib/sinon.js"></script>
<script data-main="main.js" src="../lib/require.js"></script>
<script>
window.expect = chai.expect;
mocha.setup('bdd');
</script>
<script src="../lib/openpgp.js"></script>
<script src="../lib/forge.min.js"></script>
<script src="index.js"></script>
<script>
if (window.mochaPhantomJS) {
mochaPhantomJS.run();
} else {
mocha.run();
}
</script>
</body>
</html>

View File

@ -1,61 +0,0 @@
'use strict';
// Mozilla bind polyfill because phantomjs is stupid
if (!Function.prototype.bind) {
Function.prototype.bind = function(oThis) {
if (typeof this !== "function") {
// closest thing possible to the ECMAScript 5 internal IsCallable function
throw new TypeError("Function.prototype.bind - what is trying to be bound is not callable");
}
var aArgs = Array.prototype.slice.call(arguments, 1),
fToBind = this,
FNOP = function() {},
fBound = function() {
return fToBind.apply(this instanceof FNOP && oThis ? this : oThis, aArgs.concat(Array.prototype.slice.call(arguments)));
};
FNOP.prototype = this.prototype;
fBound.prototype = new FNOP();
return fBound;
};
}
require(['src/require-config'], function() {
require.config({
baseUrl: 'src/lib',
paths: {
'browsercrow': '../../../lib/browsercrow',
'browsercrow-envelope': '../../../lib/browsercrow-envelope',
'browsercrow-bodystructure': '../../../lib/browsercrow-bodystructure',
'browsercrow-mimeparser': '../../../lib/browsercrow-mimeparser',
'browsersmtp': '../../../lib/browsersmtp'
}
});
// Start the main app logic.
require(['js/app-config', 'axe'], function(app, axe) {
window.Worker = undefined; // disable web workers since mocha doesn't support them
app.config.workerPath = '../../src/js';
//app.config.cloudUrl = 'http://localhost:8888';
axe.removeAppender(axe.defaultAppender);
startTests();
});
});
function startTests() {
mocha.setup('bdd');
require(
[
'../../email-dao-test'
], function() {
//Tests loaded, run tests
mocha.run();
}
);
}

50
test/main.js Normal file
View File

@ -0,0 +1,50 @@
'use strict';
//
// Polyfills
//
// Mozilla bind polyfill because phantomjs is stupid
if (!Function.prototype.bind) {
Function.prototype.bind = function(oThis) {
if (typeof this !== "function") {
// closest thing possible to the ECMAScript 5 internal IsCallable function
throw new TypeError("Function.prototype.bind - what is trying to be bound is not callable");
}
var aArgs = Array.prototype.slice.call(arguments, 1),
fToBind = this,
FNOP = function() {},
fBound = function() {
return fToBind.apply(this instanceof FNOP && oThis ? this : oThis, aArgs.concat(Array.prototype.slice.call(arguments)));
};
FNOP.prototype = this.prototype;
fBound.prototype = new FNOP();
return fBound;
};
}
// a warm round of applause for phantomjs for missing events
(function() {
if (!window.CustomEvent) {
var CustomEvent = function(event, params) {
params = params || {
bubbles: false,
cancelable: false,
detail: undefined
};
var evt = document.createEvent('CustomEvent');
evt.initCustomEvent(event, params.bubbles, params.cancelable, params.detail);
return evt;
};
CustomEvent.prototype = window.Event.prototype;
window.CustomEvent = CustomEvent;
}
})();
// set worker path for tests
require('../src/js/app-config').config.workerPath = '../lib';

View File

@ -1,101 +1,97 @@
define(function(require) {
'use strict';
'use strict';
var expect = chai.expect,
angular = require('angular'),
mocks = require('angularMocks'),
AccountCtrl = require('js/controller/account'),
PGP = require('js/crypto/pgp'),
dl = require('js/util/download'),
appController = require('js/app-controller'),
KeychainDAO = require('js/dao/keychain-dao');
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');
describe('Account Controller unit test', function() {
var scope, accountCtrl,
dummyFingerprint, expectedFingerprint,
dummyKeyId, expectedKeyId,
emailAddress, keySize, pgpMock, keychainMock;
describe('Account Controller unit test', function() {
var scope, accountCtrl,
dummyFingerprint, expectedFingerprint,
dummyKeyId, expectedKeyId,
emailAddress, keySize, pgpMock, keychainMock;
beforeEach(function() {
appController._pgp = pgpMock = sinon.createStubInstance(PGP);
appController._keychain = keychainMock = sinon.createStubInstance(KeychainDAO);
beforeEach(function() {
appController._pgp = pgpMock = sinon.createStubInstance(PGP);
appController._keychain = keychainMock = sinon.createStubInstance(KeychainDAO);
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);
emailAddress = 'fred@foo.com';
keySize = 1234;
appController._emailDao = {
_account: {
emailAddress: emailAddress,
asymKeySize: keySize
}
};
pgpMock.getKeyParams.returns({
_id: dummyKeyId,
fingerprint: dummyFingerprint,
userId: emailAddress,
bitSize: keySize
});
angular.module('accounttest', []);
mocks.module('accounttest');
mocks.inject(function($rootScope, $controller) {
scope = $rootScope.$new();
scope.state = {};
accountCtrl = $controller(AccountCtrl, {
$scope: scope
});
});
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);
emailAddress = 'fred@foo.com';
keySize = 1234;
appController._emailDao = {
_account: {
emailAddress: emailAddress,
asymKeySize: keySize
}
};
pgpMock.getKeyParams.returns({
_id: dummyKeyId,
fingerprint: dummyFingerprint,
userId: emailAddress,
bitSize: keySize
});
afterEach(function() {});
describe('scope variables', function() {
it('should be set correctly', function() {
expect(scope.eMail).to.equal(emailAddress);
expect(scope.keyId).to.equal(expectedKeyId);
expect(scope.fingerprint).to.equal(expectedFingerprint);
expect(scope.keysize).to.equal(keySize);
});
});
describe('export to key file', function() {
it('should work', function() {
var createDownloadMock = sinon.stub(dl, 'createDownload');
keychainMock.getUserKeyPair.withArgs(emailAddress).yields(null, {
publicKey: {
_id: dummyKeyId,
publicKey: 'a'
},
privateKey: {
encryptedKey: 'b'
}
});
createDownloadMock.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();
});
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();
};
scope.exportKeyFile();
angular.module('accounttest', []);
mocks.module('accounttest');
mocks.inject(function($rootScope, $controller) {
scope = $rootScope.$new();
scope.state = {};
accountCtrl = $controller(AccountCtrl, {
$scope: scope
});
});
});
afterEach(function() {});
describe('scope variables', function() {
it('should be set correctly', function() {
expect(scope.eMail).to.equal(emailAddress);
expect(scope.keyId).to.equal(expectedKeyId);
expect(scope.fingerprint).to.equal(expectedFingerprint);
expect(scope.keysize).to.equal(keySize);
});
});
describe('export to key file', function() {
it('should work', function() {
var createDownloadMock = sinon.stub(dl, 'createDownload');
keychainMock.getUserKeyPair.withArgs(emailAddress).yields(null, {
publicKey: {
_id: dummyKeyId,
publicKey: 'a'
},
privateKey: {
encryptedKey: 'b'
}
});
createDownloadMock.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();
});
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();
};
scope.exportKeyFile();
});
});
});

View File

@ -1,216 +1,212 @@
define(function(require) {
'use strict';
'use strict';
var expect = chai.expect,
angular = require('angular'),
mocks = require('angularMocks'),
AddAccountCtrl = require('js/controller/add-account'),
Auth = require('js/bo/auth'),
AdminDao = require('js/dao/admin-dao'),
appController = require('js/app-controller');
var mocks = angular.mock,
AddAccountCtrl = require('../../src/js/controller/add-account'),
Auth = require('../../src/js/bo/auth'),
AdminDao = require('../../src/js/dao/admin-dao'),
appController = require('../../src/js/app-controller');
describe('Add Account Controller unit test', function() {
var scope, location, ctrl, authStub, origAuth, adminStub;
describe('Add Account Controller unit test', function() {
var scope, location, ctrl, authStub, origAuth, 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);
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);
angular.module('addaccounttest', []);
mocks.module('addaccounttest');
mocks.inject(function($controller, $rootScope, $location) {
location = $location;
scope = $rootScope.$new();
scope.state = {};
scope.form = {};
scope.formValidate = {};
angular.module('addaccounttest', []);
mocks.module('addaccounttest');
mocks.inject(function($controller, $rootScope, $location) {
location = $location;
scope = $rootScope.$new();
scope.state = {};
scope.form = {};
scope.formValidate = {};
sinon.stub(location, 'path').returns(location);
sinon.stub(location, 'search').returns(location);
sinon.stub(scope, '$apply', function() {});
sinon.stub(location, 'path').returns(location);
sinon.stub(location, 'search').returns(location);
sinon.stub(scope, '$apply', function() {});
ctrl = $controller(AddAccountCtrl, {
$location: location,
$scope: scope,
$routeParams: {}
});
ctrl = $controller(AddAccountCtrl, {
$location: location,
$scope: scope,
$routeParams: {}
});
});
afterEach(function() {
// restore the app controller module
appController._auth = origAuth;
location.path.restore();
location.search.restore();
if (scope.$apply.restore) {
scope.$apply.restore();
}
});
describe('createWhiteoutAccount', function() {
it('should return early for invalid form', function() {
scope.form.$invalid = true;
scope.createWhiteoutAccount();
expect(adminStub.createUser.called).to.be.false;
});
it('should fail to error creating user', function(done) {
scope.form.$invalid = false;
scope.betaCode = 'asfd';
scope.phone = '12345';
adminStub.createUser.yieldsAsync(new Error('asdf'));
scope.$apply = function() {
expect(scope.busy).to.be.false;
expect(scope.errMsg).to.equal('asdf');
expect(adminStub.createUser.calledOnce).to.be.true;
done();
};
scope.createWhiteoutAccount();
expect(scope.busy).to.be.true;
});
it('should work', function(done) {
scope.form.$invalid = false;
scope.betaCode = 'asfd';
scope.phone = '12345';
adminStub.createUser.yieldsAsync();
scope.$apply = function() {
expect(scope.busy).to.be.false;
expect(scope.errMsg).to.be.undefined;
expect(scope.step).to.equal(3);
expect(adminStub.createUser.calledOnce).to.be.true;
done();
};
scope.createWhiteoutAccount();
expect(scope.busy).to.be.true;
});
});
describe('validateUser', function() {
it('should return early for invalid form', function() {
scope.formValidate.$invalid = true;
scope.validateUser();
expect(adminStub.validateUser.called).to.be.false;
});
it('should fail to error creating user', function(done) {
scope.formValidate.$invalid = false;
scope.token = 'asfd';
adminStub.validateUser.yieldsAsync(new Error('asdf'));
scope.$apply = function() {
expect(scope.busyValidate).to.be.false;
expect(scope.errMsgValidate).to.equal('asdf');
expect(adminStub.validateUser.calledOnce).to.be.true;
done();
};
scope.validateUser();
expect(scope.busyValidate).to.be.true;
});
it('should work', function(done) {
scope.formValidate.$invalid = false;
scope.token = 'asfd';
adminStub.validateUser.yieldsAsync();
scope.login = function() {
expect(scope.busyValidate).to.be.true;
expect(scope.errMsgValidate).to.be.undefined;
expect(adminStub.validateUser.calledOnce).to.be.true;
done();
};
scope.validateUser();
expect(scope.busyValidate).to.be.true;
});
});
describe('login', function() {
it('should work', function() {
scope.form.$invalid = false;
authStub.setCredentials.returns();
scope.login();
expect(authStub.setCredentials.calledOnce).to.be.true;
expect(location.path.calledWith('/login')).to.be.true;
});
});
describe('connectToGoogle', function() {
it('should forward to login', function() {
authStub._oauth = {
isSupported: function() {
return true;
}
};
authStub.getOAuthToken.yields();
scope.connectToGoogle();
expect(location.path.calledWith('/login-set-credentials')).to.be.true;
expect(location.search.calledWith({
provider: 'gmail'
})).to.be.true;
expect(authStub.getOAuthToken.calledOnce).to.be.true;
});
it('should not use oauth for gmail', function() {
authStub._oauth = {
isSupported: function() {
return false;
}
};
scope.connectToGoogle();
expect(location.path.calledWith('/login-set-credentials')).to.be.true;
expect(location.search.calledWith({
provider: 'gmail'
})).to.be.true;
expect(authStub.getOAuthToken.called).to.be.false;
});
it('should not forward to login when oauth fails', function(done) {
authStub._oauth = {
isSupported: function() {
return true;
}
};
authStub.getOAuthToken.yields(new Error());
scope.onError = function(err) {
expect(err).to.exist;
expect(location.path.called).to.be.false;
expect(location.search.called).to.be.false;
done();
};
scope.connectToGoogle();
});
});
describe('connectTo', function() {
it('should forward to login', function() {
var provider = 'wmail';
scope.connectTo(provider);
expect(location.path.calledWith('/login-set-credentials')).to.be.true;
expect(location.search.calledWith({
provider: provider
})).to.be.true;
});
});
});
afterEach(function() {
// restore the app controller module
appController._auth = origAuth;
location.path.restore();
location.search.restore();
if (scope.$apply.restore) {
scope.$apply.restore();
}
});
describe('createWhiteoutAccount', function() {
it('should return early for invalid form', function() {
scope.form.$invalid = true;
scope.createWhiteoutAccount();
expect(adminStub.createUser.called).to.be.false;
});
it('should fail to error creating user', function(done) {
scope.form.$invalid = false;
scope.betaCode = 'asfd';
scope.phone = '12345';
adminStub.createUser.yieldsAsync(new Error('asdf'));
scope.$apply = function() {
expect(scope.busy).to.be.false;
expect(scope.errMsg).to.equal('asdf');
expect(adminStub.createUser.calledOnce).to.be.true;
done();
};
scope.createWhiteoutAccount();
expect(scope.busy).to.be.true;
});
it('should work', function(done) {
scope.form.$invalid = false;
scope.betaCode = 'asfd';
scope.phone = '12345';
adminStub.createUser.yieldsAsync();
scope.$apply = function() {
expect(scope.busy).to.be.false;
expect(scope.errMsg).to.be.undefined;
expect(scope.step).to.equal(3);
expect(adminStub.createUser.calledOnce).to.be.true;
done();
};
scope.createWhiteoutAccount();
expect(scope.busy).to.be.true;
});
});
describe('validateUser', function() {
it('should return early for invalid form', function() {
scope.formValidate.$invalid = true;
scope.validateUser();
expect(adminStub.validateUser.called).to.be.false;
});
it('should fail to error creating user', function(done) {
scope.formValidate.$invalid = false;
scope.token = 'asfd';
adminStub.validateUser.yieldsAsync(new Error('asdf'));
scope.$apply = function() {
expect(scope.busyValidate).to.be.false;
expect(scope.errMsgValidate).to.equal('asdf');
expect(adminStub.validateUser.calledOnce).to.be.true;
done();
};
scope.validateUser();
expect(scope.busyValidate).to.be.true;
});
it('should work', function(done) {
scope.formValidate.$invalid = false;
scope.token = 'asfd';
adminStub.validateUser.yieldsAsync();
scope.login = function() {
expect(scope.busyValidate).to.be.true;
expect(scope.errMsgValidate).to.be.undefined;
expect(adminStub.validateUser.calledOnce).to.be.true;
done();
};
scope.validateUser();
expect(scope.busyValidate).to.be.true;
});
});
describe('login', function() {
it('should work', function() {
scope.form.$invalid = false;
authStub.setCredentials.returns();
scope.login();
expect(authStub.setCredentials.calledOnce).to.be.true;
expect(location.path.calledWith('/login')).to.be.true;
});
});
describe('connectToGoogle', function() {
it('should forward to login', function() {
authStub._oauth = {
isSupported: function() {
return true;
}
};
authStub.getOAuthToken.yields();
scope.connectToGoogle();
expect(location.path.calledWith('/login-set-credentials')).to.be.true;
expect(location.search.calledWith({
provider: 'gmail'
})).to.be.true;
expect(authStub.getOAuthToken.calledOnce).to.be.true;
});
it('should not use oauth for gmail', function() {
authStub._oauth = {
isSupported: function() {
return false;
}
};
scope.connectToGoogle();
expect(location.path.calledWith('/login-set-credentials')).to.be.true;
expect(location.search.calledWith({
provider: 'gmail'
})).to.be.true;
expect(authStub.getOAuthToken.called).to.be.false;
});
it('should not forward to login when oauth fails', function(done) {
authStub._oauth = {
isSupported: function() {
return true;
}
};
authStub.getOAuthToken.yields(new Error());
scope.onError = function(err) {
expect(err).to.exist;
expect(location.path.called).to.be.false;
expect(location.search.called).to.be.false;
done();
};
scope.connectToGoogle();
});
});
describe('connectTo', function() {
it('should forward to login', function() {
var provider = 'wmail';
scope.connectTo(provider);
expect(location.path.calledWith('/login-set-credentials')).to.be.true;
expect(location.search.calledWith({
provider: provider
})).to.be.true;
});
});
});

View File

@ -1,146 +1,142 @@
define(function(require) {
'use strict';
'use strict';
var RestDAO = require('js/dao/rest-dao'),
AdminDAO = require('js/dao/admin-dao'),
expect = chai.expect;
var RestDAO = require('../../src/js/dao/rest-dao'),
AdminDAO = require('../../src/js/dao/admin-dao');
describe('Admin DAO unit tests', function() {
describe('Admin DAO unit tests', function() {
var adminDao, restDaoStub,
emailAddress = 'test@example.com',
password = 'secret';
var adminDao, restDaoStub,
emailAddress = 'test@example.com',
password = 'secret';
beforeEach(function() {
restDaoStub = sinon.createStubInstance(RestDAO);
adminDao = new AdminDAO(restDaoStub);
});
beforeEach(function() {
restDaoStub = sinon.createStubInstance(RestDAO);
adminDao = new AdminDAO(restDaoStub);
});
afterEach(function() {});
afterEach(function() {});
describe('createUser', function() {
it('should fail due to incomplete args', function(done) {
var opt = {
emailAddress: emailAddress
};
describe('createUser', function() {
it('should fail due to incomplete args', function(done) {
var opt = {
emailAddress: emailAddress
};
adminDao.createUser(opt, function(err) {
expect(err).to.exist;
done();
});
});
it('should fail if user already exists', function(done) {
var opt = {
emailAddress: emailAddress,
password: password,
phone: '12345'
};
restDaoStub.post.withArgs(opt, '/user').yields({
code: 409
});
adminDao.createUser(opt, function(err) {
expect(err.message).to.contain('already taken');
expect(restDaoStub.post.calledOnce).to.be.true;
done();
});
});
it('should fail due to unknown error', function(done) {
var opt = {
emailAddress: emailAddress,
password: password,
phone: '12345'
};
restDaoStub.post.withArgs(opt, '/user').yields(new Error());
adminDao.createUser(opt, function(err) {
expect(err).to.exist;
expect(restDaoStub.post.calledOnce).to.be.true;
done();
});
});
it('should work', function(done) {
var opt = {
emailAddress: emailAddress,
password: password,
phone: '12345'
};
restDaoStub.post.withArgs(opt, '/user').yields();
adminDao.createUser(opt, function(err) {
expect(err).to.not.exist;
expect(restDaoStub.post.calledOnce).to.be.true;
done();
});
adminDao.createUser(opt, function(err) {
expect(err).to.exist;
done();
});
});
describe('validateUser', function() {
it('should fail due to incomplete args', function(done) {
var opt = {
emailAddress: emailAddress
};
it('should fail if user already exists', function(done) {
var opt = {
emailAddress: emailAddress,
password: password,
phone: '12345'
};
adminDao.validateUser(opt, function(err) {
expect(err).to.exist;
done();
});
restDaoStub.post.withArgs(opt, '/user').yields({
code: 409
});
it('should fail due to error in rest api', function(done) {
var opt = {
emailAddress: emailAddress,
token: 'H45Z6D'
};
restDaoStub.post.withArgs(opt, '/user/validate').yields(new Error());
adminDao.validateUser(opt, function(err) {
expect(err).to.exist;
expect(restDaoStub.post.calledOnce).to.be.true;
done();
});
});
it('should work with no error object', function(done) {
var opt = {
emailAddress: emailAddress,
token: 'H45Z6D'
};
restDaoStub.post.withArgs(opt, '/user/validate').yields();
adminDao.validateUser(opt, function(err) {
expect(err).to.not.exist;
expect(restDaoStub.post.calledOnce).to.be.true;
done();
});
});
it('should work with 202', function(done) {
var opt = {
emailAddress: emailAddress,
token: 'H45Z6D'
};
restDaoStub.post.withArgs(opt, '/user/validate').yields({
code: 202
});
adminDao.validateUser(opt, function(err) {
expect(err).to.not.exist;
expect(restDaoStub.post.calledOnce).to.be.true;
done();
});
adminDao.createUser(opt, function(err) {
expect(err.message).to.contain('already taken');
expect(restDaoStub.post.calledOnce).to.be.true;
done();
});
});
it('should fail due to unknown error', function(done) {
var opt = {
emailAddress: emailAddress,
password: password,
phone: '12345'
};
restDaoStub.post.withArgs(opt, '/user').yields(new Error());
adminDao.createUser(opt, function(err) {
expect(err).to.exist;
expect(restDaoStub.post.calledOnce).to.be.true;
done();
});
});
it('should work', function(done) {
var opt = {
emailAddress: emailAddress,
password: password,
phone: '12345'
};
restDaoStub.post.withArgs(opt, '/user').yields();
adminDao.createUser(opt, function(err) {
expect(err).to.not.exist;
expect(restDaoStub.post.calledOnce).to.be.true;
done();
});
});
});
describe('validateUser', function() {
it('should fail due to incomplete args', function(done) {
var opt = {
emailAddress: emailAddress
};
adminDao.validateUser(opt, function(err) {
expect(err).to.exist;
done();
});
});
it('should fail due to error in rest api', function(done) {
var opt = {
emailAddress: emailAddress,
token: 'H45Z6D'
};
restDaoStub.post.withArgs(opt, '/user/validate').yields(new Error());
adminDao.validateUser(opt, function(err) {
expect(err).to.exist;
expect(restDaoStub.post.calledOnce).to.be.true;
done();
});
});
it('should work with no error object', function(done) {
var opt = {
emailAddress: emailAddress,
token: 'H45Z6D'
};
restDaoStub.post.withArgs(opt, '/user/validate').yields();
adminDao.validateUser(opt, function(err) {
expect(err).to.not.exist;
expect(restDaoStub.post.calledOnce).to.be.true;
done();
});
});
it('should work with 202', function(done) {
var opt = {
emailAddress: emailAddress,
token: 'H45Z6D'
};
restDaoStub.post.withArgs(opt, '/user/validate').yields({
code: 202
});
adminDao.validateUser(opt, function(err) {
expect(err).to.not.exist;
expect(restDaoStub.post.calledOnce).to.be.true;
done();
});
});
});
});

View File

@ -1,212 +1,209 @@
define(function(require) {
'use strict';
'use strict';
var controller = require('js/app-controller'),
EmailDAO = require('js/dao/email-dao'),
OutboxBO = require('js/bo/outbox'),
DeviceStorageDAO = require('js/dao/devicestorage-dao'),
UpdateHandler = require('js/util/update/update-handler'),
Auth = require('js/bo/auth'),
expect = chai.expect;
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'),
Auth = require('../../src/js/bo/auth');
describe('App Controller unit tests', function() {
var emailDaoStub, outboxStub, updateHandlerStub, appConfigStoreStub, devicestorageStub, isOnlineStub, authStub;
describe('App Controller unit tests', function() {
var emailDaoStub, outboxStub, updateHandlerStub, appConfigStoreStub, devicestorageStub, isOnlineStub, authStub;
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);
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;
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);
emailAddress = 'alice@bob.com';
isOnlineStub = sinon.stub(controller, 'isOnline');
// onConnect
onConnectStub = sinon.stub(controller, 'onConnect');
});
afterEach(function() {
isOnlineStub.restore();
onConnectStub.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;
it('should fail due to error in storage initialization', function(done) {
devicestorageStub.init.withArgs(undefined).yields({});
controller.init({}, 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();
});
});
describe('start', function() {
it('should not explode', function(done) {
controller.start({
onError: function() {}
}, function(err) {
expect(err).to.not.exist;
done();
});
it('should fail due to error in update handler', function(done) {
devicestorageStub.init.yields();
updateHandlerStub.update.yields({});
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();
});
});
describe('onDisconnect', function() {
it('should work', function() {
controller.onDisconnect();
it('should fail due to error in emailDao.init', function(done) {
devicestorageStub.init.yields();
updateHandlerStub.update.yields();
emailDaoStub.init.yields({});
expect(emailDaoStub.onDisconnect.calledOnce).to.be.true;
controller.init({
emailAddress: emailAddress
}, function(err, keypair) {
expect(err).to.exist;
expect(keypair).to.not.exist;
expect(updateHandlerStub.update.calledOnce).to.be.true;
expect(emailDaoStub.init.calledOnce).to.be.true;
expect(devicestorageStub.init.calledOnce).to.be.true;
done();
});
});
describe('logout', function() {
it('should work', function(done) {
authStub.logout.yields();
emailDaoStub.onDisconnect.yields(new Error());
it('should work and return a keypair', function(done) {
devicestorageStub.init.withArgs(emailAddress).yields();
emailDaoStub.init.yields(null, {});
updateHandlerStub.update.yields();
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;
beforeEach(function() {
emailAddress = 'alice@bob.com';
// onConnect
onConnectStub = sinon.stub(controller, 'onConnect');
});
afterEach(function() {
onConnectStub.restore();
});
it('should fail due to error in storage initialization', function(done) {
devicestorageStub.init.withArgs(undefined).yields({});
controller.init({}, 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({});
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 emailDao.init', function(done) {
devicestorageStub.init.yields();
updateHandlerStub.update.yields();
emailDaoStub.init.yields({});
controller.init({
emailAddress: emailAddress
}, function(err, keypair) {
expect(err).to.exist;
expect(keypair).to.not.exist;
expect(updateHandlerStub.update.calledOnce).to.be.true;
expect(emailDaoStub.init.calledOnce).to.be.true;
expect(devicestorageStub.init.calledOnce).to.be.true;
done();
});
});
it('should work and return a keypair', function(done) {
devicestorageStub.init.withArgs(emailAddress).yields();
emailDaoStub.init.yields(null, {});
updateHandlerStub.update.yields();
controller.init({
emailAddress: emailAddress
}, function(err, keypair) {
expect(err).to.not.exist;
expect(keypair).to.exist;
expect(updateHandlerStub.update.calledOnce).to.be.true;
expect(emailDaoStub.init.calledOnce).to.be.true;
expect(devicestorageStub.init.calledOnce).to.be.true;
done();
});
controller.init({
emailAddress: emailAddress
}, function(err, keypair) {
expect(err).to.not.exist;
expect(keypair).to.exist;
expect(updateHandlerStub.update.calledOnce).to.be.true;
expect(emailDaoStub.init.calledOnce).to.be.true;
expect(devicestorageStub.init.calledOnce).to.be.true;
done();
});
});
});

View File

@ -1,377 +1,374 @@
define(function(require) {
'use strict';
'use strict';
var Auth = require('js/bo/auth'),
OAuth = require('js/util/oauth'),
PGP = require('js/crypto/pgp'),
DeviceStorageDAO = require('js/dao/devicestorage-dao'),
expect = chai.expect;
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');
describe('Auth unit tests', function() {
// Constancts
var EMAIL_ADDR_DB_KEY = 'emailaddress';
var USERNAME_DB_KEY = 'username';
var REALNAME_DB_KEY = 'realname';
var PASSWD_DB_KEY = 'password';
var PROVIDER_DB_KEY = 'provider';
var IMAP_DB_KEY = 'imap';
var SMTP_DB_KEY = 'smtp';
// SUT
var auth;
describe('Auth unit tests', function() {
// Constancts
var EMAIL_ADDR_DB_KEY = 'emailaddress';
var USERNAME_DB_KEY = 'username';
var REALNAME_DB_KEY = 'realname';
var PASSWD_DB_KEY = 'password';
var PROVIDER_DB_KEY = 'provider';
var IMAP_DB_KEY = 'imap';
var SMTP_DB_KEY = 'smtp';
// SUT
var auth;
// Dependencies
var storageStub, oauthStub, pgpStub;
// Dependencies
var storageStub, oauthStub, pgpStub;
// test data
var emailAddress = 'bla@blubb.com';
var password = 'passwordpasswordpassword';
var encryptedPassword = 'pgppasswordpgppassword';
var oauthToken = 'tokentokentokentoken';
var provider = 'gmail';
var realname = 'Bla Blubb';
var username = 'bla';
var imap = {
host: 'mail.blablubb.com',
port: 123,
secure: true,
ca: 'PEMPEMPEMPEMPEMPEMPEMPEMPEMPEM'
};
var smtp = {
host: 'mail.blablubb.com',
port: 456,
secure: true,
ca: 'PEMPEMPEMPEMPEMPEMPEMPEMPEMPEM'
};
// test data
var emailAddress = 'bla@blubb.com';
var password = 'passwordpasswordpassword';
var encryptedPassword = 'pgppasswordpgppassword';
var oauthToken = 'tokentokentokentoken';
var provider = 'gmail';
var realname = 'Bla Blubb';
var username = 'bla';
var imap = {
host: 'mail.blablubb.com',
port: 123,
secure: true,
ca: 'PEMPEMPEMPEMPEMPEMPEMPEMPEMPEM'
};
var smtp = {
host: 'mail.blablubb.com',
port: 456,
secure: true,
ca: 'PEMPEMPEMPEMPEMPEMPEMPEMPEMPEM'
};
beforeEach(function() {
storageStub = sinon.createStubInstance(DeviceStorageDAO);
oauthStub = sinon.createStubInstance(OAuth);
pgpStub = sinon.createStubInstance(PGP);
auth = new Auth(storageStub, oauthStub, pgpStub);
beforeEach(function() {
storageStub = sinon.createStubInstance(DeviceStorageDAO);
oauthStub = sinon.createStubInstance(OAuth);
pgpStub = sinon.createStubInstance(PGP);
auth = new Auth(storageStub, oauthStub, pgpStub);
});
describe('#getCredentials', function() {
it('should load credentials and retrieve credentials from cfg', function(done) {
storageStub.listItems.withArgs(EMAIL_ADDR_DB_KEY, 0, null).yieldsAsync(null, [emailAddress]);
storageStub.listItems.withArgs(PASSWD_DB_KEY, 0, null).yieldsAsync(null, [encryptedPassword]);
storageStub.listItems.withArgs(PROVIDER_DB_KEY, 0, null).yieldsAsync(null, [provider]);
storageStub.listItems.withArgs(USERNAME_DB_KEY, 0, null).yieldsAsync(null, [username]);
storageStub.listItems.withArgs(REALNAME_DB_KEY, 0, null).yieldsAsync(null, [realname]);
storageStub.listItems.withArgs(IMAP_DB_KEY, 0, null).yieldsAsync(null, [imap]);
storageStub.listItems.withArgs(SMTP_DB_KEY, 0, null).yieldsAsync(null, [smtp]);
pgpStub.decrypt.withArgs(encryptedPassword, undefined).yields(null, password);
auth.getCredentials(function(err, cred) {
expect(err).to.not.exist;
expect(auth.provider).to.equal(provider);
expect(auth.emailAddress).to.equal(emailAddress);
expect(auth.password).to.equal(password);
expect(cred.imap.host).to.equal(imap.host);
expect(cred.imap.port).to.equal(imap.port);
expect(cred.imap.secure).to.equal(imap.secure);
expect(cred.imap.ca).to.equal(imap.ca);
expect(cred.imap.auth.user).to.equal(username);
expect(cred.imap.auth.pass).to.equal(password);
expect(cred.smtp.host).to.equal(smtp.host);
expect(cred.smtp.port).to.equal(smtp.port);
expect(cred.smtp.secure).to.equal(smtp.secure);
expect(cred.smtp.ca).to.equal(smtp.ca);
expect(cred.smtp.auth.user).to.equal(username);
expect(cred.smtp.auth.pass).to.equal(password);
expect(storageStub.listItems.callCount).to.equal(7);
expect(pgpStub.decrypt.calledOnce).to.be.true;
done();
});
});
});
describe('#setCredentials', function() {
it('should set the credentials', function() {
auth.setCredentials({
provider: 'albhsvadlbvsdalbsadflb',
emailAddress: emailAddress,
username: username,
realname: realname,
password: password,
imap: imap,
smtp: smtp
});
expect(auth.provider).to.equal('albhsvadlbvsdalbsadflb');
expect(auth.emailAddress).to.equal(emailAddress);
expect(auth.username).to.equal(username);
expect(auth.realname).to.equal(realname);
expect(auth.password).to.equal(password);
expect(auth.smtp).to.equal(smtp);
expect(auth.imap).to.equal(imap);
expect(auth.credentialsDirty).to.be.true;
});
describe('#getCredentials', function() {
it('should load credentials and retrieve credentials from cfg', function(done) {
storageStub.listItems.withArgs(EMAIL_ADDR_DB_KEY, 0, null).yieldsAsync(null, [emailAddress]);
storageStub.listItems.withArgs(PASSWD_DB_KEY, 0, null).yieldsAsync(null, [encryptedPassword]);
storageStub.listItems.withArgs(PROVIDER_DB_KEY, 0, null).yieldsAsync(null, [provider]);
storageStub.listItems.withArgs(USERNAME_DB_KEY, 0, null).yieldsAsync(null, [username]);
storageStub.listItems.withArgs(REALNAME_DB_KEY, 0, null).yieldsAsync(null, [realname]);
storageStub.listItems.withArgs(IMAP_DB_KEY, 0, null).yieldsAsync(null, [imap]);
storageStub.listItems.withArgs(SMTP_DB_KEY, 0, null).yieldsAsync(null, [smtp]);
pgpStub.decrypt.withArgs(encryptedPassword, undefined).yields(null, password);
});
auth.getCredentials(function(err, cred) {
expect(err).to.not.exist;
describe('#storeCredentials', function() {
it('should persist ALL the things!', function(done) {
auth.credentialsDirty = true;
auth.emailAddress = emailAddress;
auth.username = username;
auth.realname = realname;
auth.password = password;
auth.smtp = smtp;
auth.imap = imap;
auth.provider = provider;
expect(auth.provider).to.equal(provider);
expect(auth.emailAddress).to.equal(emailAddress);
expect(auth.password).to.equal(password);
storageStub.storeList.withArgs([encryptedPassword], PASSWD_DB_KEY).yieldsAsync();
storageStub.storeList.withArgs([emailAddress], EMAIL_ADDR_DB_KEY).yieldsAsync();
storageStub.storeList.withArgs([provider], PROVIDER_DB_KEY).yieldsAsync();
storageStub.storeList.withArgs([username], USERNAME_DB_KEY).yieldsAsync();
storageStub.storeList.withArgs([realname], REALNAME_DB_KEY).yieldsAsync();
storageStub.storeList.withArgs([imap], IMAP_DB_KEY).yieldsAsync();
storageStub.storeList.withArgs([smtp], SMTP_DB_KEY).yieldsAsync();
pgpStub.encrypt.withArgs(password).yields(null, encryptedPassword);
expect(cred.imap.host).to.equal(imap.host);
expect(cred.imap.port).to.equal(imap.port);
expect(cred.imap.secure).to.equal(imap.secure);
expect(cred.imap.ca).to.equal(imap.ca);
expect(cred.imap.auth.user).to.equal(username);
expect(cred.imap.auth.pass).to.equal(password);
auth.storeCredentials(function(err) {
expect(err).to.not.exist;
expect(cred.smtp.host).to.equal(smtp.host);
expect(cred.smtp.port).to.equal(smtp.port);
expect(cred.smtp.secure).to.equal(smtp.secure);
expect(cred.smtp.ca).to.equal(smtp.ca);
expect(cred.smtp.auth.user).to.equal(username);
expect(cred.smtp.auth.pass).to.equal(password);
expect(storageStub.storeList.callCount).to.equal(7);
expect(pgpStub.encrypt.calledOnce).to.be.true;
expect(storageStub.listItems.callCount).to.equal(7);
expect(pgpStub.decrypt.calledOnce).to.be.true;
done();
});
});
});
done();
});
describe('#getOAuthToken', function() {
it('should refresh token with known email address', function(done) {
auth.emailAddress = emailAddress;
auth.oauthToken = 'oldToken';
oauthStub.refreshToken.withArgs({
emailAddress: emailAddress,
oldToken: 'oldToken'
}).yieldsAsync(null, oauthToken);
auth.getOAuthToken(function(err) {
expect(err).to.not.exist;
expect(auth.emailAddress).to.equal(emailAddress);
expect(auth.oauthToken).to.equal(oauthToken);
expect(oauthStub.refreshToken.calledOnce).to.be.true;
done();
});
});
describe('#setCredentials', function() {
it('should set the credentials', function() {
auth.setCredentials({
provider: 'albhsvadlbvsdalbsadflb',
emailAddress: emailAddress,
username: username,
realname: realname,
password: password,
imap: imap,
smtp: smtp
});
it('should fetch token with known email address', function(done) {
auth.emailAddress = emailAddress;
oauthStub.getOAuthToken.withArgs(emailAddress).yieldsAsync(null, oauthToken);
expect(auth.provider).to.equal('albhsvadlbvsdalbsadflb');
auth.getOAuthToken(function(err) {
expect(err).to.not.exist;
expect(auth.emailAddress).to.equal(emailAddress);
expect(auth.oauthToken).to.equal(oauthToken);
expect(oauthStub.getOAuthToken.calledOnce).to.be.true;
done();
});
});
it('should fetch token with unknown email address', function(done) {
oauthStub.getOAuthToken.withArgs(undefined).yieldsAsync(null, oauthToken);
oauthStub.queryEmailAddress.withArgs(oauthToken).yieldsAsync(null, emailAddress);
auth.getOAuthToken(function(err) {
expect(err).to.not.exist;
expect(auth.emailAddress).to.equal(emailAddress);
expect(auth.oauthToken).to.equal(oauthToken);
expect(oauthStub.getOAuthToken.calledOnce).to.be.true;
expect(oauthStub.queryEmailAddress.calledOnce).to.be.true;
done();
});
});
it('should fail when email address fetch fails', function(done) {
oauthStub.getOAuthToken.yieldsAsync(null, oauthToken);
oauthStub.queryEmailAddress.yieldsAsync(new Error());
auth.getOAuthToken(function(err) {
expect(err).to.exist;
expect(auth.emailAddress).to.not.exist;
expect(auth.oauthToken).to.not.exist;
expect(oauthStub.getOAuthToken.calledOnce).to.be.true;
expect(oauthStub.queryEmailAddress.calledOnce).to.be.true;
done();
});
});
it('should fail when oauth fetch fails', function(done) {
oauthStub.getOAuthToken.yieldsAsync(new Error());
auth.getOAuthToken(function(err) {
expect(err).to.exist;
expect(auth.emailAddress).to.not.exist;
expect(auth.oauthToken).to.not.exist;
expect(oauthStub.getOAuthToken.calledOnce).to.be.true;
expect(oauthStub.queryEmailAddress.called).to.be.false;
done();
});
});
});
describe('#_loadCredentials', function() {
it('should work', function(done) {
storageStub.listItems.withArgs(EMAIL_ADDR_DB_KEY, 0, null).yieldsAsync(null, [emailAddress]);
storageStub.listItems.withArgs(PASSWD_DB_KEY, 0, null).yieldsAsync(null, [encryptedPassword]);
storageStub.listItems.withArgs(PROVIDER_DB_KEY, 0, null).yieldsAsync(null, [provider]);
storageStub.listItems.withArgs(USERNAME_DB_KEY, 0, null).yieldsAsync(null, [username]);
storageStub.listItems.withArgs(REALNAME_DB_KEY, 0, null).yieldsAsync(null, [realname]);
storageStub.listItems.withArgs(IMAP_DB_KEY, 0, null).yieldsAsync(null, [imap]);
storageStub.listItems.withArgs(SMTP_DB_KEY, 0, null).yieldsAsync(null, [smtp]);
auth._loadCredentials(function(err) {
expect(err).to.not.exist;
expect(auth.emailAddress).to.equal(emailAddress);
expect(auth.password).to.equal(encryptedPassword);
expect(auth.provider).to.equal(provider);
expect(auth.imap).to.equal(imap);
expect(auth.smtp).to.equal(smtp);
expect(auth.username).to.equal(username);
expect(auth.realname).to.equal(realname);
expect(auth.password).to.equal(password);
expect(auth.smtp).to.equal(smtp);
expect(auth.imap).to.equal(imap);
expect(auth.credentialsDirty).to.be.true;
});
});
expect(auth.passwordNeedsDecryption).to.be.true;
describe('#storeCredentials', function() {
it('should persist ALL the things!', function(done) {
auth.credentialsDirty = true;
auth.emailAddress = emailAddress;
auth.username = username;
auth.realname = realname;
auth.password = password;
auth.smtp = smtp;
auth.imap = imap;
auth.provider = provider;
expect(storageStub.listItems.callCount).to.equal(7);
storageStub.storeList.withArgs([encryptedPassword], PASSWD_DB_KEY).yieldsAsync();
storageStub.storeList.withArgs([emailAddress], EMAIL_ADDR_DB_KEY).yieldsAsync();
storageStub.storeList.withArgs([provider], PROVIDER_DB_KEY).yieldsAsync();
storageStub.storeList.withArgs([username], USERNAME_DB_KEY).yieldsAsync();
storageStub.storeList.withArgs([realname], REALNAME_DB_KEY).yieldsAsync();
storageStub.storeList.withArgs([imap], IMAP_DB_KEY).yieldsAsync();
storageStub.storeList.withArgs([smtp], SMTP_DB_KEY).yieldsAsync();
pgpStub.encrypt.withArgs(password).yields(null, encryptedPassword);
auth.storeCredentials(function(err) {
expect(err).to.not.exist;
expect(storageStub.storeList.callCount).to.equal(7);
expect(pgpStub.encrypt.calledOnce).to.be.true;
done();
});
done();
});
});
describe('#getOAuthToken', function() {
it('should refresh token with known email address', function(done) {
auth.emailAddress = emailAddress;
auth.oauthToken = 'oldToken';
it('should fail', function(done) {
storageStub.listItems.yieldsAsync(new Error());
oauthStub.refreshToken.withArgs({
emailAddress: emailAddress,
oldToken: 'oldToken'
}).yieldsAsync(null, oauthToken);
auth._loadCredentials(function(err) {
expect(err).to.exist;
expect(auth.emailAddress).to.not.exist;
expect(auth.password).to.not.exist;
expect(auth.provider).to.not.exist;
expect(auth.imap).to.not.exist;
expect(auth.smtp).to.not.exist;
expect(auth.username).to.not.exist;
expect(auth.realname).to.not.exist;
auth.getOAuthToken(function(err) {
expect(err).to.not.exist;
expect(auth.emailAddress).to.equal(emailAddress);
expect(auth.oauthToken).to.equal(oauthToken);
expect(storageStub.listItems.calledOnce).to.be.true;
expect(oauthStub.refreshToken.calledOnce).to.be.true;
done();
});
});
it('should fetch token with known email address', function(done) {
auth.emailAddress = emailAddress;
oauthStub.getOAuthToken.withArgs(emailAddress).yieldsAsync(null, oauthToken);
auth.getOAuthToken(function(err) {
expect(err).to.not.exist;
expect(auth.emailAddress).to.equal(emailAddress);
expect(auth.oauthToken).to.equal(oauthToken);
expect(oauthStub.getOAuthToken.calledOnce).to.be.true;
done();
});
});
it('should fetch token with unknown email address', function(done) {
oauthStub.getOAuthToken.withArgs(undefined).yieldsAsync(null, oauthToken);
oauthStub.queryEmailAddress.withArgs(oauthToken).yieldsAsync(null, emailAddress);
auth.getOAuthToken(function(err) {
expect(err).to.not.exist;
expect(auth.emailAddress).to.equal(emailAddress);
expect(auth.oauthToken).to.equal(oauthToken);
expect(oauthStub.getOAuthToken.calledOnce).to.be.true;
expect(oauthStub.queryEmailAddress.calledOnce).to.be.true;
done();
});
});
it('should fail when email address fetch fails', function(done) {
oauthStub.getOAuthToken.yieldsAsync(null, oauthToken);
oauthStub.queryEmailAddress.yieldsAsync(new Error());
auth.getOAuthToken(function(err) {
expect(err).to.exist;
expect(auth.emailAddress).to.not.exist;
expect(auth.oauthToken).to.not.exist;
expect(oauthStub.getOAuthToken.calledOnce).to.be.true;
expect(oauthStub.queryEmailAddress.calledOnce).to.be.true;
done();
});
});
it('should fail when oauth fetch fails', function(done) {
oauthStub.getOAuthToken.yieldsAsync(new Error());
auth.getOAuthToken(function(err) {
expect(err).to.exist;
expect(auth.emailAddress).to.not.exist;
expect(auth.oauthToken).to.not.exist;
expect(oauthStub.getOAuthToken.calledOnce).to.be.true;
expect(oauthStub.queryEmailAddress.called).to.be.false;
done();
});
done();
});
});
});
describe('#_loadCredentials', function() {
it('should work', function(done) {
storageStub.listItems.withArgs(EMAIL_ADDR_DB_KEY, 0, null).yieldsAsync(null, [emailAddress]);
storageStub.listItems.withArgs(PASSWD_DB_KEY, 0, null).yieldsAsync(null, [encryptedPassword]);
storageStub.listItems.withArgs(PROVIDER_DB_KEY, 0, null).yieldsAsync(null, [provider]);
storageStub.listItems.withArgs(USERNAME_DB_KEY, 0, null).yieldsAsync(null, [username]);
storageStub.listItems.withArgs(REALNAME_DB_KEY, 0, null).yieldsAsync(null, [realname]);
storageStub.listItems.withArgs(IMAP_DB_KEY, 0, null).yieldsAsync(null, [imap]);
storageStub.listItems.withArgs(SMTP_DB_KEY, 0, null).yieldsAsync(null, [smtp]);
describe('#handleCertificateUpdate', function() {
var storeCredentialsStub;
var dummyCert = 'cert';
auth._loadCredentials(function(err) {
expect(err).to.not.exist;
expect(auth.emailAddress).to.equal(emailAddress);
expect(auth.password).to.equal(encryptedPassword);
expect(auth.provider).to.equal(provider);
expect(auth.imap).to.equal(imap);
expect(auth.smtp).to.equal(smtp);
expect(auth.username).to.equal(username);
expect(auth.realname).to.equal(realname);
function onConnectDummy() {}
expect(auth.passwordNeedsDecryption).to.be.true;
expect(storageStub.listItems.callCount).to.equal(7);
done();
});
});
it('should fail', function(done) {
storageStub.listItems.yieldsAsync(new Error());
auth._loadCredentials(function(err) {
expect(err).to.exist;
expect(auth.emailAddress).to.not.exist;
expect(auth.password).to.not.exist;
expect(auth.provider).to.not.exist;
expect(auth.imap).to.not.exist;
expect(auth.smtp).to.not.exist;
expect(auth.username).to.not.exist;
expect(auth.realname).to.not.exist;
expect(storageStub.listItems.calledOnce).to.be.true;
done();
});
});
beforeEach(function() {
storeCredentialsStub = sinon.stub(auth, 'storeCredentials');
});
describe('#handleCertificateUpdate', function() {
var storeCredentialsStub;
var dummyCert = 'cert';
it('should work for Trust on first use', function(done) {
auth.imap = {};
storeCredentialsStub.yields();
function onConnectDummy() {}
function callback(err) {
expect(err).to.not.exist;
expect(storeCredentialsStub.callCount).to.equal(1);
done();
}
auth.handleCertificateUpdate('imap', onConnectDummy, callback, dummyCert);
});
beforeEach(function() {
storeCredentialsStub = sinon.stub(auth, 'storeCredentials');
});
it('should work for stored cert', function() {
auth.imap = {
ca: dummyCert
};
storeCredentialsStub.yields();
it('should work for Trust on first use', function(done) {
auth.imap = {};
storeCredentialsStub.yields();
auth.handleCertificateUpdate('imap', onConnectDummy, onConnectDummy, dummyCert);
expect(storeCredentialsStub.callCount).to.equal(0);
});
function callback(err) {
expect(err).to.not.exist;
expect(storeCredentialsStub.callCount).to.equal(1);
done();
}
auth.handleCertificateUpdate('imap', onConnectDummy, callback, dummyCert);
});
it('should work for pinned cert', function(done) {
auth.imap = {
ca: 'other',
pinned: true
};
storeCredentialsStub.yields();
it('should work for stored cert', function() {
auth.imap = {
ca: dummyCert
};
storeCredentialsStub.yields();
auth.handleCertificateUpdate('imap', onConnectDummy, onConnectDummy, dummyCert);
function callback(err) {
expect(err).to.exist;
expect(err.message).to.exist;
expect(storeCredentialsStub.callCount).to.equal(0);
});
done();
}
auth.handleCertificateUpdate('imap', onConnectDummy, callback, dummyCert);
});
it('should work for pinned cert', function(done) {
auth.imap = {
ca: 'other',
pinned: true
};
storeCredentialsStub.yields();
it('should work for updated cert', function(done) {
auth.imap = {
ca: 'other'
};
storeCredentialsStub.yields();
function callback(err) {
function callback(err) {
if (err && err.callback) {
expect(err).to.exist;
expect(err.message).to.exist;
expect(storeCredentialsStub.callCount).to.equal(0);
err.callback(true);
} else {
expect(storeCredentialsStub.callCount).to.equal(1);
done();
}
auth.handleCertificateUpdate('imap', onConnectDummy, callback, dummyCert);
});
}
it('should work for updated cert', function(done) {
auth.imap = {
ca: 'other'
};
storeCredentialsStub.yields();
function onConnect(callback) {
callback();
}
function callback(err) {
if (err && err.callback) {
expect(err).to.exist;
expect(err.message).to.exist;
expect(storeCredentialsStub.callCount).to.equal(0);
err.callback(true);
} else {
expect(storeCredentialsStub.callCount).to.equal(1);
done();
}
}
auth.handleCertificateUpdate('imap', onConnect, callback, dummyCert);
});
});
function onConnect(callback) {
callback();
}
describe('#logout', function() {
it('should fail to to error in calling db clear', function(done) {
storageStub.clear.yields(new Error());
auth.handleCertificateUpdate('imap', onConnect, callback, dummyCert);
auth.logout(function(err) {
expect(err).to.exist;
done();
});
});
describe('#logout', function() {
it('should fail to to error in calling db clear', function(done) {
storageStub.clear.yields(new Error());
it('should work', function(done) {
storageStub.clear.yields();
auth.logout(function(err) {
expect(err).to.exist;
done();
});
});
it('should work', function(done) {
storageStub.clear.yields();
auth.logout(function(err) {
expect(err).to.not.exist;
expect(auth.password).to.be.undefined;
expect(auth.initialized).to.be.undefined;
expect(auth.credentialsDirty).to.be.undefined;
expect(auth.passwordNeedsDecryption).to.be.undefined;
done();
});
auth.logout(function(err) {
expect(err).to.not.exist;
expect(auth.password).to.be.undefined;
expect(auth.initialized).to.be.undefined;
expect(auth.credentialsDirty).to.be.undefined;
expect(auth.passwordNeedsDecryption).to.be.undefined;
done();
});
});
});

View File

@ -1,67 +1,64 @@
define(function(require) {
'use strict';
'use strict';
var btnHandler = require('js/util/backbutton-handler'),
expect = chai.expect;
var btnHandler = require('../../src/js/util/backbutton-handler');
describe('Backbutton Handler', function() {
chai.Assertion.includeStack = true;
describe('Backbutton Handler', function() {
chai.Assertion.includeStack = true;
var scope, event;
var scope, event;
beforeEach(function() {
scope = {
state: {},
$apply: function() {}
};
beforeEach(function() {
scope = {
state: {},
$apply: function() {}
};
event = new CustomEvent('backbutton');
event = new CustomEvent('backbutton');
// this is a precondition for the test. throw an exception
// if this would produce side effects
expect(navigator.app).to.not.exist;
navigator.app = {};
// this is a precondition for the test. throw an exception
// if this would produce side effects
expect(navigator.app).to.not.exist;
navigator.app = {};
btnHandler.attachHandler(scope);
btnHandler.start();
});
btnHandler.attachHandler(scope);
btnHandler.start();
});
afterEach(function() {
btnHandler.stop();
delete navigator.app;
});
afterEach(function() {
btnHandler.stop();
delete navigator.app;
});
it('should close lightbox', function() {
scope.state.lightbox = 'asd';
document.dispatchEvent(event);
expect(scope.state.lightbox).to.be.undefined;
});
it('should close lightbox', function() {
scope.state.lightbox = 'asd';
document.dispatchEvent(event);
expect(scope.state.lightbox).to.be.undefined;
});
it('should close reader', function() {
scope.state.read = {
open: true,
toggle: function(state) {
scope.state.read.open = state;
}
};
document.dispatchEvent(event);
expect(scope.state.read.open).to.be.false;
});
it('should close reader', function() {
scope.state.read = {
open: true,
toggle: function(state) {
scope.state.read.open = state;
}
};
document.dispatchEvent(event);
expect(scope.state.read.open).to.be.false;
});
it('should close navigation', function() {
scope.state.nav = {
open: true,
toggle: function(state) {
scope.state.nav.open = state;
}
};
document.dispatchEvent(event);
expect(scope.state.nav.open).to.be.false;
});
it('should close navigation', function() {
scope.state.nav = {
open: true,
toggle: function(state) {
scope.state.nav.open = state;
}
};
document.dispatchEvent(event);
expect(scope.state.nav.open).to.be.false;
});
it('should close app', function(done) {
navigator.app.exitApp = done;
document.dispatchEvent(event);
});
it('should close app', function(done) {
navigator.app.exitApp = done;
document.dispatchEvent(event);
});
});

View File

@ -1,414 +1,415 @@
define(function(require) {
'use strict';
'use strict';
var ConnectionDoctor = require('js/util/connection-doctor'),
TCPSocket = require('tcp-socket'),
ImapClient = require('imap-client'),
SmtpClient = require('smtpclient'),
cfg = require('js/app-config').config,
expect = chai.expect;
var TCPSocket = require('tcp-socket'),
ImapClient = require('imap-client'),
SmtpClient = require('wo-smtpclient'),
ConnectionDoctor = require('../../src/js/util/connection-doctor'),
cfg = require('../../src/js/app-config').config;
describe('Connection Doctor', function() {
var doctor;
var socketStub, imapStub, smtpStub, credentials;
describe('Connection Doctor', function() {
var doctor;
var socketStub, imapStub, smtpStub, credentials, workerPath;
beforeEach(function() {
//
// Stubs
//
beforeEach(function() {
//
// Stubs
//
// there is no socket shim for for this use case, use dummy object
socketStub = {
close: function() {
this.onclose();
// there is no socket shim for for this use case, use dummy object
socketStub = {
close: function() {
this.onclose();
}
};
workerPath = 'js/tcp-socket-tls-worker.min.js';
imapStub = sinon.createStubInstance(ImapClient);
smtpStub = sinon.createStubInstance(SmtpClient);
//
// Fixture
//
credentials = {
imap: {
host: 'asd',
port: 1234,
secure: true,
ca: 'cert'
},
smtp: {
host: 'qwe',
port: 5678,
secure: false,
ca: 'cert'
},
username: 'username',
password: 'password'
};
sinon.stub(TCPSocket, 'open').returns(socketStub); // convenience constructors suck
//
// Setup SUT
//
doctor = new ConnectionDoctor();
doctor.configure(credentials);
doctor._imap = imapStub;
doctor._smtp = smtpStub;
});
afterEach(function() {
TCPSocket.open.restore();
});
describe('#_checkOnline', function() {
it('should check if browser is online', function(done) {
doctor._checkOnline(function(error) {
if (navigator.onLine) {
expect(error).to.not.exist;
} else {
expect(error).to.exist;
expect(error.code).to.equal(ConnectionDoctor.OFFLINE);
}
};
imapStub = sinon.createStubInstance(ImapClient);
smtpStub = sinon.createStubInstance(SmtpClient);
//
// Fixture
//
credentials = {
imap: {
host: 'asd',
port: 1234,
secure: true,
ca: 'cert'
},
smtp: {
host: 'qwe',
port: 5678,
secure: false,
ca: 'cert'
},
username: 'username',
password: 'password'
};
sinon.stub(TCPSocket, 'open').returns(socketStub); // convenience constructors suck
//
// Setup SUT
//
doctor = new ConnectionDoctor();
doctor.configure(credentials);
doctor._imap = imapStub;
doctor._smtp = smtpStub;
});
afterEach(function() {
TCPSocket.open.restore();
});
describe('#_checkOnline', function() {
it('should check if browser is online', function(done) {
doctor._checkOnline(function(error) {
if (navigator.onLine) {
expect(error).to.not.exist;
} else {
expect(error).to.exist;
expect(error.code).to.equal(ConnectionDoctor.OFFLINE);
}
done();
});
});
});
describe('#_checkReachable', function() {
it('should be able to reach the host w/o cert', function(done) {
credentials.imap.ca = undefined;
doctor._checkReachable(credentials.imap, function(error) {
expect(error).to.not.exist;
expect(TCPSocket.open.calledOnce).to.be.true;
expect(TCPSocket.open.calledWith(credentials.imap.host, credentials.imap.port, {
binaryType: 'arraybuffer',
useSecureTransport: credentials.imap.secure,
ca: credentials.imap.ca
})).to.be.true;
done();
});
socketStub.oncert();
socketStub.onopen();
});
it('should catch Mozilla TCPSocket exception', function(done) {
// Mozilla forbids extensions to the TCPSocket object
Object.defineProperty(socketStub, 'oncert', {
set: function() {
throw 'Mozilla specific behavior';
}
});
doctor._checkReachable(credentials.imap, function(error) {
expect(error).to.not.exist;
expect(TCPSocket.open.calledOnce).to.be.true;
expect(TCPSocket.open.calledWith(credentials.imap.host, credentials.imap.port, {
binaryType: 'arraybuffer',
useSecureTransport: credentials.imap.secure,
ca: credentials.imap.ca
})).to.be.true;
done();
});
socketStub.onopen();
});
it('should fail w/ wrong cert', function(done) {
doctor._checkReachable(credentials.imap, function(error) {
expect(error).to.exist;
expect(error.code).to.equal(ConnectionDoctor.TLS_WRONG_CERT);
expect(TCPSocket.open.calledOnce).to.be.true;
expect(TCPSocket.open.calledWith(credentials.imap.host, credentials.imap.port, {
binaryType: 'arraybuffer',
useSecureTransport: credentials.imap.secure,
ca: credentials.imap.ca
})).to.be.true;
done();
});
socketStub.oncert();
socketStub.onerror();
socketStub.onclose();
});
it('should fail w/ host unreachable', function(done) {
doctor._checkReachable(credentials.imap, function(error) {
expect(error).to.exist;
expect(error.code).to.equal(ConnectionDoctor.HOST_UNREACHABLE);
expect(TCPSocket.open.calledOnce).to.be.true;
done();
});
socketStub.onerror({
data: new Error()
});
socketStub.onclose();
});
it('should fail w/ timeout', function(done) {
var origTimeout = cfg.connDocTimeout; // remember timeout from the config to reset it on done
cfg.connDocTimeout = 20; // set to 20ms for the test
doctor._checkReachable(credentials.imap, function(error) {
expect(error).to.exist;
expect(error.code).to.equal(ConnectionDoctor.HOST_TIMEOUT);
expect(TCPSocket.open.calledOnce).to.be.true;
cfg.connDocTimeout = origTimeout;
done();
});
});
});
describe('#_checkImap', function() {
it('should perform IMAP login, list folders, logout', function(done) {
imapStub.login.yieldsAsync();
imapStub.listWellKnownFolders.yieldsAsync(null, {
Inbox: [{}]
});
imapStub.logout.yieldsAsync();
doctor._checkImap(function(error) {
expect(error).to.not.exist;
expect(imapStub.login.calledOnce).to.be.true;
expect(imapStub.listWellKnownFolders.calledOnce).to.be.true;
expect(imapStub.logout.calledOnce).to.be.true;
done();
});
});
it('should fail w/ generic error on logout', function(done) {
imapStub.login.yieldsAsync();
imapStub.listWellKnownFolders.yieldsAsync(null, {
Inbox: [{}]
});
doctor._checkImap(function(error) {
expect(error).to.exist;
expect(error.code).to.equal(ConnectionDoctor.GENERIC_ERROR);
expect(error.underlyingError).to.exist;
expect(imapStub.login.calledOnce).to.be.true;
expect(imapStub.listWellKnownFolders.calledOnce).to.be.true;
expect(imapStub.logout.calledOnce).to.be.true;
done();
});
setTimeout(function() {
// this error is thrown while we're waiting for the logout
imapStub.onError(new Error());
}, 50);
});
it('should fail w/ generic error on inbox missing', function(done) {
imapStub.login.yieldsAsync();
imapStub.listWellKnownFolders.yieldsAsync(null, {
Inbox: []
});
doctor._checkImap(function(error) {
expect(error).to.exist;
expect(error.code).to.equal(ConnectionDoctor.NO_INBOX);
expect(imapStub.login.calledOnce).to.be.true;
expect(imapStub.listWellKnownFolders.calledOnce).to.be.true;
expect(imapStub.logout.called).to.be.false;
done();
});
});
it('should fail w/ generic error on listing folders fails', function(done) {
imapStub.login.yieldsAsync();
imapStub.listWellKnownFolders.yieldsAsync(new Error());
doctor._checkImap(function(error) {
expect(error).to.exist;
expect(error.code).to.equal(ConnectionDoctor.GENERIC_ERROR);
expect(error.underlyingError).to.exist;
expect(imapStub.login.calledOnce).to.be.true;
expect(imapStub.listWellKnownFolders.calledOnce).to.be.true;
expect(imapStub.logout.called).to.be.false;
done();
});
});
it('should fail w/ auth rejected', function(done) {
doctor._checkImap(function(error) {
expect(error).to.exist;
expect(error.code).to.equal(ConnectionDoctor.AUTH_REJECTED);
expect(error.underlyingError).to.exist;
expect(imapStub.login.calledOnce).to.be.true;
expect(imapStub.listWellKnownFolders.called).to.be.false;
expect(imapStub.logout.called).to.be.false;
done();
});
setTimeout(function() {
// this error is thrown while we're waiting for the login
imapStub.onError(new Error());
}, 50);
});
});
describe('#_checkSmtp', function() {
it('should perform SMTP login, logout', function(done) {
doctor._checkSmtp(function(error) {
expect(error).to.not.exist;
expect(smtpStub.connect.calledOnce).to.be.true;
expect(smtpStub.quit.calledOnce).to.be.true;
done();
});
smtpStub.onidle();
smtpStub.onclose();
});
it('should fail w/ auth rejected', function(done) {
doctor._checkSmtp(function(error) {
expect(error).to.exist;
expect(error.code).to.equal(ConnectionDoctor.AUTH_REJECTED);
expect(error.underlyingError).to.exist;
expect(smtpStub.connect.calledOnce).to.be.true;
expect(smtpStub.quit.called).to.be.false;
done();
});
smtpStub.onerror(new Error());
});
});
describe('#check', function() {
beforeEach(function() {
sinon.stub(doctor, '_checkOnline');
sinon.stub(doctor, '_checkReachable');
sinon.stub(doctor, '_checkImap');
sinon.stub(doctor, '_checkSmtp');
});
it('should perform all tests', function(done) {
doctor._checkOnline.yieldsAsync();
doctor._checkReachable.withArgs(credentials.imap).yieldsAsync();
doctor._checkReachable.withArgs(credentials.smtp).yieldsAsync();
doctor._checkImap.yieldsAsync();
doctor._checkSmtp.yieldsAsync();
doctor.check(function(err) {
expect(err).to.not.exist;
expect(doctor._checkOnline.calledOnce).to.be.true;
expect(doctor._checkReachable.calledTwice).to.be.true;
expect(doctor._checkImap.calledOnce).to.be.true;
expect(doctor._checkSmtp.calledOnce).to.be.true;
done();
});
});
it('should fail for smtp', function(done) {
doctor._checkOnline.yieldsAsync();
doctor._checkReachable.withArgs(credentials.imap).yieldsAsync();
doctor._checkReachable.withArgs(credentials.smtp).yieldsAsync();
doctor._checkImap.yieldsAsync();
doctor._checkSmtp.yieldsAsync(new Error());
doctor.check(function(err) {
expect(err).to.exist;
expect(doctor._checkOnline.calledOnce).to.be.true;
expect(doctor._checkReachable.calledTwice).to.be.true;
expect(doctor._checkImap.calledOnce).to.be.true;
expect(doctor._checkSmtp.calledOnce).to.be.true;
done();
});
});
it('should fail for imap', function(done) {
doctor._checkOnline.yieldsAsync();
doctor._checkReachable.withArgs(credentials.imap).yieldsAsync();
doctor._checkReachable.withArgs(credentials.smtp).yieldsAsync();
doctor._checkImap.yieldsAsync(new Error());
doctor.check(function(err) {
expect(err).to.exist;
expect(doctor._checkOnline.calledOnce).to.be.true;
expect(doctor._checkReachable.calledTwice).to.be.true;
expect(doctor._checkImap.calledOnce).to.be.true;
expect(doctor._checkSmtp.called).to.be.false;
done();
});
});
it('should fail for smtp reachability', function(done) {
doctor._checkOnline.yieldsAsync();
doctor._checkReachable.withArgs(credentials.imap).yieldsAsync();
doctor._checkReachable.withArgs(credentials.smtp).yieldsAsync(new Error());
doctor.check(function(err) {
expect(err).to.exist;
expect(doctor._checkOnline.calledOnce).to.be.true;
expect(doctor._checkReachable.calledTwice).to.be.true;
expect(doctor._checkImap.called).to.be.false;
expect(doctor._checkSmtp.called).to.be.false;
done();
});
});
it('should fail for imap reachability', function(done) {
doctor._checkOnline.yieldsAsync();
doctor._checkReachable.withArgs(credentials.imap).yieldsAsync(new Error());
doctor.check(function(err) {
expect(err).to.exist;
expect(doctor._checkOnline.calledOnce).to.be.true;
expect(doctor._checkReachable.calledOnce).to.be.true;
expect(doctor._checkImap.called).to.be.false;
expect(doctor._checkSmtp.called).to.be.false;
done();
});
});
it('should fail for offline', function(done) {
doctor._checkOnline.yieldsAsync(new Error());
doctor.check(function(err) {
expect(err).to.exist;
expect(doctor._checkOnline.calledOnce).to.be.true;
expect(doctor._checkReachable.called).to.be.false;
expect(doctor._checkImap.called).to.be.false;
expect(doctor._checkSmtp.called).to.be.false;
done();
});
});
it('should fail w/o config', function(done) {
doctor.credentials = doctor._imap = doctor._smtp = undefined;
doctor.check(function(err) {
expect(err).to.exist;
expect(doctor._checkOnline.called).to.be.false;
expect(doctor._checkReachable.called).to.be.false;
expect(doctor._checkImap.called).to.be.false;
expect(doctor._checkSmtp.called).to.be.false;
done();
});
done();
});
});
});
});
describe('#_checkReachable', function() {
it('should be able to reach the host w/o cert', function(done) {
credentials.imap.ca = undefined;
doctor._checkReachable(credentials.imap, function(error) {
expect(error).to.not.exist;
expect(TCPSocket.open.calledOnce).to.be.true;
expect(TCPSocket.open.calledWith(credentials.imap.host, credentials.imap.port, {
binaryType: 'arraybuffer',
useSecureTransport: credentials.imap.secure,
ca: credentials.imap.ca,
tlsWorkerPath: workerPath
})).to.be.true;
done();
});
socketStub.oncert();
socketStub.onopen();
});
it('should catch Mozilla TCPSocket exception', function(done) {
// Mozilla forbids extensions to the TCPSocket object
Object.defineProperty(socketStub, 'oncert', {
set: function() {
throw 'Mozilla specific behavior';
}
});
doctor._checkReachable(credentials.imap, function(error) {
expect(error).to.not.exist;
expect(TCPSocket.open.calledOnce).to.be.true;
expect(TCPSocket.open.calledWith(credentials.imap.host, credentials.imap.port, {
binaryType: 'arraybuffer',
useSecureTransport: credentials.imap.secure,
ca: credentials.imap.ca,
tlsWorkerPath: workerPath
})).to.be.true;
done();
});
socketStub.onopen();
});
it('should fail w/ wrong cert', function(done) {
doctor._checkReachable(credentials.imap, function(error) {
expect(error).to.exist;
expect(error.code).to.equal(ConnectionDoctor.TLS_WRONG_CERT);
expect(TCPSocket.open.calledOnce).to.be.true;
expect(TCPSocket.open.calledWith(credentials.imap.host, credentials.imap.port, {
binaryType: 'arraybuffer',
useSecureTransport: credentials.imap.secure,
ca: credentials.imap.ca,
tlsWorkerPath: workerPath
})).to.be.true;
done();
});
socketStub.oncert();
socketStub.onerror();
socketStub.onclose();
});
it('should fail w/ host unreachable', function(done) {
doctor._checkReachable(credentials.imap, function(error) {
expect(error).to.exist;
expect(error.code).to.equal(ConnectionDoctor.HOST_UNREACHABLE);
expect(TCPSocket.open.calledOnce).to.be.true;
done();
});
socketStub.onerror({
data: new Error()
});
socketStub.onclose();
});
it('should fail w/ timeout', function(done) {
var origTimeout = cfg.connDocTimeout; // remember timeout from the config to reset it on done
cfg.connDocTimeout = 20; // set to 20ms for the test
doctor._checkReachable(credentials.imap, function(error) {
expect(error).to.exist;
expect(error.code).to.equal(ConnectionDoctor.HOST_TIMEOUT);
expect(TCPSocket.open.calledOnce).to.be.true;
cfg.connDocTimeout = origTimeout;
done();
});
});
});
describe('#_checkImap', function() {
it('should perform IMAP login, list folders, logout', function(done) {
imapStub.login.yieldsAsync();
imapStub.listWellKnownFolders.yieldsAsync(null, {
Inbox: [{}]
});
imapStub.logout.yieldsAsync();
doctor._checkImap(function(error) {
expect(error).to.not.exist;
expect(imapStub.login.calledOnce).to.be.true;
expect(imapStub.listWellKnownFolders.calledOnce).to.be.true;
expect(imapStub.logout.calledOnce).to.be.true;
done();
});
});
it('should fail w/ generic error on logout', function(done) {
imapStub.login.yieldsAsync();
imapStub.listWellKnownFolders.yieldsAsync(null, {
Inbox: [{}]
});
doctor._checkImap(function(error) {
expect(error).to.exist;
expect(error.code).to.equal(ConnectionDoctor.GENERIC_ERROR);
expect(error.underlyingError).to.exist;
expect(imapStub.login.calledOnce).to.be.true;
expect(imapStub.listWellKnownFolders.calledOnce).to.be.true;
expect(imapStub.logout.calledOnce).to.be.true;
done();
});
setTimeout(function() {
// this error is thrown while we're waiting for the logout
imapStub.onError(new Error());
}, 50);
});
it('should fail w/ generic error on inbox missing', function(done) {
imapStub.login.yieldsAsync();
imapStub.listWellKnownFolders.yieldsAsync(null, {
Inbox: []
});
doctor._checkImap(function(error) {
expect(error).to.exist;
expect(error.code).to.equal(ConnectionDoctor.NO_INBOX);
expect(imapStub.login.calledOnce).to.be.true;
expect(imapStub.listWellKnownFolders.calledOnce).to.be.true;
expect(imapStub.logout.called).to.be.false;
done();
});
});
it('should fail w/ generic error on listing folders fails', function(done) {
imapStub.login.yieldsAsync();
imapStub.listWellKnownFolders.yieldsAsync(new Error());
doctor._checkImap(function(error) {
expect(error).to.exist;
expect(error.code).to.equal(ConnectionDoctor.GENERIC_ERROR);
expect(error.underlyingError).to.exist;
expect(imapStub.login.calledOnce).to.be.true;
expect(imapStub.listWellKnownFolders.calledOnce).to.be.true;
expect(imapStub.logout.called).to.be.false;
done();
});
});
it('should fail w/ auth rejected', function(done) {
doctor._checkImap(function(error) {
expect(error).to.exist;
expect(error.code).to.equal(ConnectionDoctor.AUTH_REJECTED);
expect(error.underlyingError).to.exist;
expect(imapStub.login.calledOnce).to.be.true;
expect(imapStub.listWellKnownFolders.called).to.be.false;
expect(imapStub.logout.called).to.be.false;
done();
});
setTimeout(function() {
// this error is thrown while we're waiting for the login
imapStub.onError(new Error());
}, 50);
});
});
describe('#_checkSmtp', function() {
it('should perform SMTP login, logout', function(done) {
doctor._checkSmtp(function(error) {
expect(error).to.not.exist;
expect(smtpStub.connect.calledOnce).to.be.true;
expect(smtpStub.quit.calledOnce).to.be.true;
done();
});
smtpStub.onidle();
smtpStub.onclose();
});
it('should fail w/ auth rejected', function(done) {
doctor._checkSmtp(function(error) {
expect(error).to.exist;
expect(error.code).to.equal(ConnectionDoctor.AUTH_REJECTED);
expect(error.underlyingError).to.exist;
expect(smtpStub.connect.calledOnce).to.be.true;
expect(smtpStub.quit.called).to.be.false;
done();
});
smtpStub.onerror(new Error());
});
});
describe('#check', function() {
beforeEach(function() {
sinon.stub(doctor, '_checkOnline');
sinon.stub(doctor, '_checkReachable');
sinon.stub(doctor, '_checkImap');
sinon.stub(doctor, '_checkSmtp');
});
it('should perform all tests', function(done) {
doctor._checkOnline.yieldsAsync();
doctor._checkReachable.withArgs(credentials.imap).yieldsAsync();
doctor._checkReachable.withArgs(credentials.smtp).yieldsAsync();
doctor._checkImap.yieldsAsync();
doctor._checkSmtp.yieldsAsync();
doctor.check(function(err) {
expect(err).to.not.exist;
expect(doctor._checkOnline.calledOnce).to.be.true;
expect(doctor._checkReachable.calledTwice).to.be.true;
expect(doctor._checkImap.calledOnce).to.be.true;
expect(doctor._checkSmtp.calledOnce).to.be.true;
done();
});
});
it('should fail for smtp', function(done) {
doctor._checkOnline.yieldsAsync();
doctor._checkReachable.withArgs(credentials.imap).yieldsAsync();
doctor._checkReachable.withArgs(credentials.smtp).yieldsAsync();
doctor._checkImap.yieldsAsync();
doctor._checkSmtp.yieldsAsync(new Error());
doctor.check(function(err) {
expect(err).to.exist;
expect(doctor._checkOnline.calledOnce).to.be.true;
expect(doctor._checkReachable.calledTwice).to.be.true;
expect(doctor._checkImap.calledOnce).to.be.true;
expect(doctor._checkSmtp.calledOnce).to.be.true;
done();
});
});
it('should fail for imap', function(done) {
doctor._checkOnline.yieldsAsync();
doctor._checkReachable.withArgs(credentials.imap).yieldsAsync();
doctor._checkReachable.withArgs(credentials.smtp).yieldsAsync();
doctor._checkImap.yieldsAsync(new Error());
doctor.check(function(err) {
expect(err).to.exist;
expect(doctor._checkOnline.calledOnce).to.be.true;
expect(doctor._checkReachable.calledTwice).to.be.true;
expect(doctor._checkImap.calledOnce).to.be.true;
expect(doctor._checkSmtp.called).to.be.false;
done();
});
});
it('should fail for smtp reachability', function(done) {
doctor._checkOnline.yieldsAsync();
doctor._checkReachable.withArgs(credentials.imap).yieldsAsync();
doctor._checkReachable.withArgs(credentials.smtp).yieldsAsync(new Error());
doctor.check(function(err) {
expect(err).to.exist;
expect(doctor._checkOnline.calledOnce).to.be.true;
expect(doctor._checkReachable.calledTwice).to.be.true;
expect(doctor._checkImap.called).to.be.false;
expect(doctor._checkSmtp.called).to.be.false;
done();
});
});
it('should fail for imap reachability', function(done) {
doctor._checkOnline.yieldsAsync();
doctor._checkReachable.withArgs(credentials.imap).yieldsAsync(new Error());
doctor.check(function(err) {
expect(err).to.exist;
expect(doctor._checkOnline.calledOnce).to.be.true;
expect(doctor._checkReachable.calledOnce).to.be.true;
expect(doctor._checkImap.called).to.be.false;
expect(doctor._checkSmtp.called).to.be.false;
done();
});
});
it('should fail for offline', function(done) {
doctor._checkOnline.yieldsAsync(new Error());
doctor.check(function(err) {
expect(err).to.exist;
expect(doctor._checkOnline.calledOnce).to.be.true;
expect(doctor._checkReachable.called).to.be.false;
expect(doctor._checkImap.called).to.be.false;
expect(doctor._checkSmtp.called).to.be.false;
done();
});
});
it('should fail w/o config', function(done) {
doctor.credentials = doctor._imap = doctor._smtp = undefined;
doctor.check(function(err) {
expect(err).to.exist;
expect(doctor._checkOnline.called).to.be.false;
expect(doctor._checkReachable.called).to.be.false;
expect(doctor._checkImap.called).to.be.false;
expect(doctor._checkSmtp.called).to.be.false;
done();
});
});
});
});

View File

@ -1,190 +1,186 @@
define(function(require) {
'use strict';
'use strict';
var expect = chai.expect,
angular = require('angular'),
mocks = require('angularMocks'),
ContactsCtrl = require('js/controller/contacts'),
appController = require('js/app-controller'),
KeychainDAO = require('js/dao/keychain-dao'),
PGP = require('js/crypto/pgp');
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');
describe('Contacts Controller unit test', function() {
var scope, contactsCtrl,
origKeychain, keychainMock,
origPgp, pgpMock;
describe('Contacts Controller unit test', function() {
var scope, contactsCtrl,
origKeychain, keychainMock,
origPgp, pgpMock;
beforeEach(function() {
origPgp = appController._pgp;
appController._pgp = pgpMock = sinon.createStubInstance(PGP);
origKeychain = appController._keychain;
appController._keychain = keychainMock = sinon.createStubInstance(KeychainDAO);
beforeEach(function() {
origPgp = appController._pgp;
appController._pgp = pgpMock = sinon.createStubInstance(PGP);
origKeychain = appController._keychain;
appController._keychain = keychainMock = sinon.createStubInstance(KeychainDAO);
angular.module('contactstest', []);
mocks.module('contactstest');
mocks.inject(function($rootScope, $controller) {
scope = $rootScope.$new();
scope.state = {};
contactsCtrl = $controller(ContactsCtrl, {
$scope: scope
});
});
});
afterEach(function() {
// restore the module
appController._pgp = origPgp;
appController._keychain = origKeychain;
});
describe('scope variables', function() {
it('should be set correctly', function() {
expect(scope.fingerprint).to.equal('XXXX XXXX XXXX XXXX XXXX XXXX XXXX XXXX XXXX XXXX');
expect(scope.state.contacts.toggle).to.exist;
});
});
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();
};
scope.listKeys();
});
it('should work', function(done) {
keychainMock.listLocalPublicKeys.yields(null, [{
_id: '12345'
}]);
pgpMock.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();
});
});
describe('getFingerprint', function() {
it('should work', function() {
var key = {
fingerprint: 'YYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYY'
};
scope.getFingerprint(key);
expect(scope.fingerprint).to.equal('YYYY YYYY YYYY YYYY YYYY ... YYYY YYYY YYYY YYYY YYYY');
});
});
describe('importKey', function() {
it('should work', function(done) {
var keyArmored = '-----BEGIN PGP PUBLIC KEY BLOCK-----';
pgpMock.getKeyParams.returns({
_id: '12345',
userId: 'max@example.com',
userIds: []
});
keychainMock.saveLocalPublicKey.withArgs({
_id: '12345',
userId: 'max@example.com',
userIds: [],
publicKey: '-----BEGIN PGP PUBLIC KEY BLOCK-----',
imported: true
}).yields();
scope.listKeys = function() {
done();
};
scope.importKey(keyArmored);
});
it('should fail due to invalid armored key', function(done) {
var keyArmored = '-----BEGIN PGP PRIVATE KEY BLOCK-----';
scope.onError = function(err) {
expect(err).to.exist;
done();
};
scope.importKey(keyArmored);
});
it('should fail due to error in pgp.getKeyParams', function(done) {
var keyArmored = '-----BEGIN PGP PUBLIC KEY BLOCK-----';
pgpMock.getKeyParams.throws(new Error('WAT'));
scope.onError = function(err) {
expect(err).to.exist;
done();
};
scope.importKey(keyArmored);
});
it('should fail due to error in keychain.saveLocalPublicKey', function(done) {
var keyArmored = '-----BEGIN PGP PUBLIC KEY BLOCK-----';
pgpMock.getKeyParams.returns({
_id: '12345',
userId: 'max@example.com'
});
keychainMock.saveLocalPublicKey.yields(42);
scope.onError = function(err) {
expect(err).to.equal(42);
done();
};
scope.importKey(keyArmored);
});
});
describe('removeKey', function() {
it('should work', function(done) {
var key = {
_id: '12345'
};
keychainMock.removeLocalPublicKey.withArgs('12345').yields();
scope.listKeys = function() {
done();
};
scope.removeKey(key);
});
it('should fail due to error in keychain.removeLocalPublicKey', function(done) {
var key = {
_id: '12345'
};
keychainMock.removeLocalPublicKey.withArgs('12345').yields(42);
scope.onError = function(err) {
expect(err).to.equal(42);
done();
};
scope.removeKey(key);
angular.module('contactstest', []);
mocks.module('contactstest');
mocks.inject(function($rootScope, $controller) {
scope = $rootScope.$new();
scope.state = {};
contactsCtrl = $controller(ContactsCtrl, {
$scope: scope
});
});
});
afterEach(function() {
// restore the module
appController._pgp = origPgp;
appController._keychain = origKeychain;
});
describe('scope variables', function() {
it('should be set correctly', function() {
expect(scope.fingerprint).to.equal('XXXX XXXX XXXX XXXX XXXX XXXX XXXX XXXX XXXX XXXX');
expect(scope.state.contacts.toggle).to.exist;
});
});
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();
};
scope.listKeys();
});
it('should work', function(done) {
keychainMock.listLocalPublicKeys.yields(null, [{
_id: '12345'
}]);
pgpMock.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();
});
});
describe('getFingerprint', function() {
it('should work', function() {
var key = {
fingerprint: 'YYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYY'
};
scope.getFingerprint(key);
expect(scope.fingerprint).to.equal('YYYY YYYY YYYY YYYY YYYY ... YYYY YYYY YYYY YYYY YYYY');
});
});
describe('importKey', function() {
it('should work', function(done) {
var keyArmored = '-----BEGIN PGP PUBLIC KEY BLOCK-----';
pgpMock.getKeyParams.returns({
_id: '12345',
userId: 'max@example.com',
userIds: []
});
keychainMock.saveLocalPublicKey.withArgs({
_id: '12345',
userId: 'max@example.com',
userIds: [],
publicKey: '-----BEGIN PGP PUBLIC KEY BLOCK-----',
imported: true
}).yields();
scope.listKeys = function() {
done();
};
scope.importKey(keyArmored);
});
it('should fail due to invalid armored key', function(done) {
var keyArmored = '-----BEGIN PGP PRIVATE KEY BLOCK-----';
scope.onError = function(err) {
expect(err).to.exist;
done();
};
scope.importKey(keyArmored);
});
it('should fail due to error in pgp.getKeyParams', function(done) {
var keyArmored = '-----BEGIN PGP PUBLIC KEY BLOCK-----';
pgpMock.getKeyParams.throws(new Error('WAT'));
scope.onError = function(err) {
expect(err).to.exist;
done();
};
scope.importKey(keyArmored);
});
it('should fail due to error in keychain.saveLocalPublicKey', function(done) {
var keyArmored = '-----BEGIN PGP PUBLIC KEY BLOCK-----';
pgpMock.getKeyParams.returns({
_id: '12345',
userId: 'max@example.com'
});
keychainMock.saveLocalPublicKey.yields(42);
scope.onError = function(err) {
expect(err).to.equal(42);
done();
};
scope.importKey(keyArmored);
});
});
describe('removeKey', function() {
it('should work', function(done) {
var key = {
_id: '12345'
};
keychainMock.removeLocalPublicKey.withArgs('12345').yields();
scope.listKeys = function() {
done();
};
scope.removeKey(key);
});
it('should fail due to error in keychain.removeLocalPublicKey', function(done) {
var key = {
_id: '12345'
};
keychainMock.removeLocalPublicKey.withArgs('12345').yields(42);
scope.onError = function(err) {
expect(err).to.equal(42);
done();
};
scope.removeKey(key);
});
});
});

View File

@ -1,58 +1,55 @@
define(function(require) {
'use strict';
'use strict';
var Crypto = require('js/crypto/crypto'),
util = require('js/crypto/util'),
config = require('js/app-config').config,
expect = chai.expect;
var Crypto = require('../../src/js/crypto/crypto'),
config = require('../../src/js/app-config').config,
util = require('crypto-lib').util;
describe('Crypto unit tests', function() {
this.timeout(20000);
describe('Crypto unit tests', function() {
this.timeout(20000);
var crypto,
password = 'password',
keySize = config.symKeySize,
ivSize = config.symIvSize;
var crypto,
password = 'password',
keySize = config.symKeySize,
ivSize = config.symIvSize;
beforeEach(function() {
crypto = new Crypto();
});
beforeEach(function() {
crypto = new Crypto();
});
afterEach(function() {});
afterEach(function() {});
describe('AES encrypt/decrypt', function() {
it('should work', function(done) {
var plaintext = 'Hello, World!';
var key = util.random(keySize);
var iv = util.random(ivSize);
describe('AES encrypt/decrypt', function() {
it('should work', function(done) {
var plaintext = 'Hello, World!';
var key = util.random(keySize);
var iv = util.random(ivSize);
crypto.encrypt(plaintext, key, iv, function(err, ciphertext) {
crypto.encrypt(plaintext, key, iv, function(err, ciphertext) {
expect(err).to.not.exist;
expect(ciphertext).to.exist;
crypto.decrypt(ciphertext, key, iv, function(err, decrypted) {
expect(err).to.not.exist;
expect(ciphertext).to.exist;
crypto.decrypt(ciphertext, key, iv, function(err, decrypted) {
expect(err).to.not.exist;
expect(decrypted).to.equal(plaintext);
done();
});
});
});
});
describe("PBKDF2 (Async/Worker)", function() {
it('should work', function(done) {
var salt = util.random(keySize);
crypto.deriveKey(password, salt, keySize, function(err, key) {
expect(err).to.not.exist;
expect(util.base642Str(key).length * 8).to.equal(keySize);
expect(decrypted).to.equal(plaintext);
done();
});
});
});
});
describe("PBKDF2 (Async/Worker)", function() {
it('should work', function(done) {
var salt = util.random(keySize);
crypto.deriveKey(password, salt, keySize, function(err, key) {
expect(err).to.not.exist;
expect(util.base642Str(key).length * 8).to.equal(keySize);
done();
});
});
});
});

View File

@ -1,105 +1,101 @@
define(function(require) {
'use strict';
'use strict';
var LawnchairDAO = require('js/dao/lawnchair-dao'),
DeviceStorageDAO = require('js/dao/devicestorage-dao'),
expect = chai.expect;
var LawnchairDAO = require('../../src/js/dao/lawnchair-dao'),
DeviceStorageDAO = require('../../src/js/dao/devicestorage-dao');
var testUser = 'test@example.com';
var testUser = 'test@example.com';
describe('Device Storage DAO unit tests', function() {
describe('Device Storage DAO unit tests', function() {
var storageDao, lawnchairDaoStub;
var storageDao, lawnchairDaoStub;
beforeEach(function() {
lawnchairDaoStub = sinon.createStubInstance(LawnchairDAO);
storageDao = new DeviceStorageDAO(lawnchairDaoStub);
beforeEach(function() {
lawnchairDaoStub = sinon.createStubInstance(LawnchairDAO);
storageDao = new DeviceStorageDAO(lawnchairDaoStub);
});
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();
});
});
});
afterEach(function() {});
describe('store list', function() {
it('should fail', function(done) {
var list = [{}];
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();
});
storageDao.storeList(list, '', function(err) {
expect(err).to.exist;
done();
});
});
describe('store list', function() {
it('should fail', function(done) {
var list = [{}];
it('should work with empty list', function(done) {
var list = [];
storageDao.storeList(list, '', function(err) {
expect(err).to.exist;
done();
});
});
it('should work with empty list', function(done) {
var list = [];
storageDao.storeList(list, 'email', function(err) {
expect(err).to.not.exist;
done();
});
});
it('should work', function(done) {
lawnchairDaoStub.batch.yields();
var list = [{
foo: 'bar'
}];
storageDao.storeList(list, 'email', function(err) {
expect(err).to.not.exist;
expect(lawnchairDaoStub.batch.calledOnce).to.be.true;
done();
});
storageDao.storeList(list, 'email', function(err) {
expect(err).to.not.exist;
done();
});
});
describe('remove list', function() {
it('should work', function(done) {
lawnchairDaoStub.removeList.yields();
it('should work', function(done) {
lawnchairDaoStub.batch.yields();
storageDao.removeList('email', function(err) {
expect(err).to.not.exist;
expect(lawnchairDaoStub.removeList.calledOnce).to.be.true;
done();
});
var list = [{
foo: 'bar'
}];
storageDao.storeList(list, 'email', function(err) {
expect(err).to.not.exist;
expect(lawnchairDaoStub.batch.calledOnce).to.be.true;
done();
});
});
});
describe('list items', function() {
it('should work', function(done) {
lawnchairDaoStub.list.yields();
describe('remove list', function() {
it('should work', function(done) {
lawnchairDaoStub.removeList.yields();
storageDao.listItems('email', 0, null, function(err) {
expect(err).to.not.exist;
expect(lawnchairDaoStub.list.calledOnce).to.be.true;
done();
});
storageDao.removeList('email', function(err) {
expect(err).to.not.exist;
expect(lawnchairDaoStub.removeList.calledOnce).to.be.true;
done();
});
});
});
describe('clear', function() {
it('should work', function(done) {
lawnchairDaoStub.clear.yields();
describe('list items', function() {
it('should work', function(done) {
lawnchairDaoStub.list.yields();
storageDao.clear(function(err) {
expect(err).to.not.exist;
expect(lawnchairDaoStub.clear.calledOnce).to.be.true;
done();
});
storageDao.listItems('email', 0, null, function(err) {
expect(err).to.not.exist;
expect(lawnchairDaoStub.list.calledOnce).to.be.true;
done();
});
});
});
describe('clear', function() {
it('should work', function(done) {
lawnchairDaoStub.clear.yields();
storageDao.clear(function(err) {
expect(err).to.not.exist;
expect(lawnchairDaoStub.clear.calledOnce).to.be.true;
done();
});
});
});
});

View File

@ -1,50 +1,46 @@
define(function(require) {
'use strict';
'use strict';
var expect = chai.expect,
angular = require('angular'),
mocks = require('angularMocks'),
DialogCtrl = require('js/controller/dialog');
var mocks = angular.mock,
DialogCtrl = require('../../src/js/controller/dialog');
describe('Dialog Controller unit test', function() {
var scope, dialogCtrl;
describe('Dialog Controller unit test', function() {
var scope, dialogCtrl;
beforeEach(function() {
angular.module('dialogtest', []);
mocks.module('dialogtest');
mocks.inject(function($rootScope, $controller) {
scope = $rootScope.$new();
scope.state = {
dialog: {}
};
dialogCtrl = $controller(DialogCtrl, {
$scope: scope
});
});
});
afterEach(function() {});
describe('confirm', function() {
it('should work', function(done) {
scope.state.dialog.callback = function(confirmed) {
expect(confirmed).to.be.true;
expect(scope.state.dialog.open).to.be.false;
done();
};
scope.confirm(true);
});
});
describe('cancel', function() {
it('should work', function(done) {
scope.state.dialog.callback = function(confirmed) {
expect(confirmed).to.be.false;
expect(scope.state.dialog.open).to.be.false;
done();
};
scope.confirm(false);
beforeEach(function() {
angular.module('dialogtest', []);
mocks.module('dialogtest');
mocks.inject(function($rootScope, $controller) {
scope = $rootScope.$new();
scope.state = {
dialog: {}
};
dialogCtrl = $controller(DialogCtrl, {
$scope: scope
});
});
});
afterEach(function() {});
describe('confirm', function() {
it('should work', function(done) {
scope.state.dialog.callback = function(confirmed) {
expect(confirmed).to.be.true;
expect(scope.state.dialog.open).to.be.false;
done();
};
scope.confirm(true);
});
});
describe('cancel', function() {
it('should work', function(done) {
scope.state.dialog.callback = function(confirmed) {
expect(confirmed).to.be.false;
expect(scope.state.dialog.open).to.be.false;
done();
};
scope.confirm(false);
});
});
});

File diff suppressed because it is too large Load Diff

View File

@ -8,12 +8,29 @@
</head>
<body>
<div id="mocha"></div>
<script src="../lib/chai.js"></script>
<script src="../lib/sinon.js"></script>
<script src="../lib/mocha.js"></script>
<script data-main="main.js" src="../../src/lib/require.js"></script>
<script src="../lib/chai.js"></script>
<script src="../lib/mocha.js"></script>
<script src="../lib/sinon.js"></script>
<script>
window.expect = chai.expect;
mocha.setup('bdd');
</script>
<script src="../lib/openpgp.js"></script>
<script src="../lib/forge.min.js"></script>
<script src="index.js"></script>
<script>
mocha.checkLeaks();
mocha.globals(['chrome']);
if (window.mochaPhantomJS) {
mochaPhantomJS.run();
} else {
mocha.run();
}
</script>
</body>
</html>

View File

@ -1,108 +1,105 @@
define(function(require) {
'use strict';
'use strict';
var RestDAO = require('js/dao/rest-dao'),
InvitationDAO = require('js/dao/invitation-dao'),
expect = chai.expect;
var RestDAO = require('../../src/js/dao/rest-dao'),
InvitationDAO = require('../../src/js/dao/invitation-dao');
describe('Invitation DAO unit tests', function() {
var restDaoStub, invitationDao,
alice = 'zuhause@aol.com',
bob = 'manfred.mustermann@musterdomain.com',
expectedUri = '/invitation/recipient/' + alice + '/sender/' + bob;
describe('Invitation DAO unit tests', function() {
var restDaoStub, invitationDao,
alice = 'zuhause@aol.com',
bob = 'manfred.mustermann@musterdomain.com',
expectedUri = '/invitation/recipient/' + alice + '/sender/' + bob;
beforeEach(function() {
restDaoStub = sinon.createStubInstance(RestDAO);
invitationDao = new InvitationDAO(restDaoStub);
beforeEach(function() {
restDaoStub = sinon.createStubInstance(RestDAO);
invitationDao = new InvitationDAO(restDaoStub);
});
describe('initialization', function() {
it('should wire up correctly', function() {
expect(invitationDao._restDao).to.equal(restDaoStub);
expect(invitationDao.invite).to.exist;
expect(InvitationDAO.INVITE_MISSING).to.equal(1);
expect(InvitationDAO.INVITE_PENDING).to.equal(2);
expect(InvitationDAO.INVITE_SUCCESS).to.equal(4);
});
});
describe('initialization', function() {
it('should wire up correctly', function() {
expect(invitationDao._restDao).to.equal(restDaoStub);
expect(invitationDao.invite).to.exist;
expect(InvitationDAO.INVITE_MISSING).to.equal(1);
expect(InvitationDAO.INVITE_PENDING).to.equal(2);
expect(InvitationDAO.INVITE_SUCCESS).to.equal(4);
describe('invite', function() {
it('should invite the recipient', function(done) {
restDaoStub.put.yieldsAsync(null, undefined, 201);
invitationDao.invite({
recipient: alice,
sender: bob
}, function(err, status) {
expect(err).to.not.exist;
expect(status).to.equal(InvitationDAO.INVITE_SUCCESS);
expect(restDaoStub.put.calledWith({}, expectedUri)).to.be.true;
done();
});
});
describe('invite', function() {
it('should invite the recipient', function(done) {
restDaoStub.put.yieldsAsync(null, undefined, 201);
it('should point out already invited recipient', function(done) {
restDaoStub.put.yieldsAsync(null, undefined, 304);
invitationDao.invite({
recipient: alice,
sender: bob
}, function(err, status) {
expect(err).to.not.exist;
expect(status).to.equal(InvitationDAO.INVITE_SUCCESS);
expect(restDaoStub.put.calledWith({}, expectedUri)).to.be.true;
done();
});
invitationDao.invite({
recipient: alice,
sender: bob
}, function(err, status) {
expect(err).to.not.exist;
expect(status).to.equal(InvitationDAO.INVITE_PENDING);
done();
});
});
it('should not work for http error', function(done) {
restDaoStub.put.yieldsAsync({
errMsg: 'jawollja.'
});
it('should point out already invited recipient', function(done) {
restDaoStub.put.yieldsAsync(null, undefined, 304);
invitationDao.invite({
recipient: alice,
sender: bob
}, function(err, status) {
expect(err).to.not.exist;
expect(status).to.equal(InvitationDAO.INVITE_PENDING);
done();
});
invitationDao.invite({
recipient: alice,
sender: bob
}, function(err, status) {
expect(err).to.exist;
expect(status).to.not.exist;
done();
});
});
it('should not work for http error', function(done) {
restDaoStub.put.yieldsAsync({
errMsg: 'jawollja.'
});
it('should not work for unexpected response', function(done) {
restDaoStub.put.yieldsAsync(null, undefined, 1337);
invitationDao.invite({
recipient: alice,
sender: bob
}, function(err, status) {
expect(err).to.exist;
expect(status).to.not.exist;
done();
});
invitationDao.invite({
recipient: alice,
sender: bob
}, function(err, status) {
expect(err).to.exist;
expect(status).to.not.exist;
done();
});
});
it('should not work for unexpected response', function(done) {
restDaoStub.put.yieldsAsync(null, undefined, 1337);
it('should report erroneous usage', function() {
invitationDao.invite({
sender: bob
}, expectError);
invitationDao.invite({
recipient: alice,
sender: bob
}, function(err, status) {
expect(err).to.exist;
expect(status).to.not.exist;
done();
});
});
invitationDao.invite({
recipient: alice,
}, expectError);
it('should report erroneous usage', function() {
invitationDao.invite({
sender: bob
}, expectError);
invitationDao.invite({
recipient: 123,
sender: 123
}, expectError);
invitationDao.invite({
recipient: alice,
}, expectError);
invitationDao.invite('asd', expectError);
invitationDao.invite({
recipient: 123,
sender: 123
}, expectError);
invitationDao.invite('asd', expectError);
function expectError(err, status) {
expect(err).to.exist;
expect(status).to.not.exist;
}
});
function expectError(err, status) {
expect(err).to.exist;
expect(status).to.not.exist;
}
});
});
});

File diff suppressed because it is too large Load Diff

View File

@ -1,152 +1,148 @@
define(function(require) {
'use strict';
'use strict';
var LawnchairDAO = require('js/dao/lawnchair-dao'),
expect = chai.expect;
var LawnchairDAO = require('../../src/js/dao/lawnchair-dao');
var dbName = 'lawnchair@test.com';
var dbName = 'lawnchair@test.com';
var key = 'type_1';
var data = {
name: 'testName1',
type: 'testType1'
};
var key = 'type_1';
var data = {
name: 'testName1',
type: 'testType1'
};
var key2 = 'type_2';
var data2 = {
name: 'testName2',
type: 'testType2'
};
var key2 = 'type_2';
var data2 = {
name: 'testName2',
type: 'testType2'
};
describe('Lawnchair DAO unit tests', function() {
var lawnchairDao;
describe('Lawnchair DAO unit tests', function() {
var lawnchairDao;
beforeEach(function(done) {
lawnchairDao = new LawnchairDAO();
lawnchairDao.init(dbName, function(err) {
expect(err).to.not.exist;
expect(lawnchairDao._db).to.exist;
beforeEach(function(done) {
lawnchairDao = new LawnchairDAO();
lawnchairDao.init(dbName, function(err) {
expect(err).to.not.exist;
expect(lawnchairDao._db).to.exist;
done();
});
});
afterEach(function(done) {
lawnchairDao.clear(function(err) {
expect(err).to.not.exist;
done();
});
});
describe('read', function() {
it('should fail', function(done) {
lawnchairDao.read(undefined, function(err) {
expect(err).to.exist;
done();
});
});
});
describe('list', function() {
it('should fail', function(done) {
lawnchairDao.list(undefined, 0, null, function(err) {
expect(err).to.exist;
done();
});
});
});
describe('remove list', function() {
it('should fail', function(done) {
lawnchairDao.removeList(undefined, function(err) {
expect(err).to.exist;
done();
});
});
});
describe('persist/read/remove', function() {
it('should fail', function(done) {
lawnchairDao.persist(undefined, data, function(err) {
expect(err).to.exist;
done();
});
});
it('should fail', function(done) {
lawnchairDao.persist('1234', undefined, function(err) {
expect(err).to.exist;
done();
});
});
afterEach(function(done) {
lawnchairDao.clear(function(err) {
it('should work', function(done) {
lawnchairDao.persist(key, data, function(err) {
expect(err).to.not.exist;
lawnchairDao.read(key, onRead);
});
function onRead(err, fetched) {
expect(err).to.not.exist;
expect(fetched).to.deep.equal(data);
lawnchairDao.remove(key, onRemove);
}
function onRemove(err) {
expect(err).to.not.exist;
lawnchairDao.read(key, onReadAgain);
}
function onReadAgain(err, fetched) {
expect(err).to.not.exist;
expect(fetched).to.not.exist;
done();
}
});
});
describe('batch/list/removeList', function() {
it('should fails', function(done) {
lawnchairDao.batch({}, function(err) {
expect(err).to.exist;
done();
});
});
describe('read', function() {
it('should fail', function(done) {
lawnchairDao.read(undefined, function(err) {
expect(err).to.exist;
done();
});
it('should work', function(done) {
var list = [{
key: key,
object: data
}, {
key: key2,
object: data2
}];
lawnchairDao.batch(list, function(err) {
expect(err).to.not.exist;
lawnchairDao.list('type', 0, null, onList);
});
function onList(err, fetched) {
expect(err).to.not.exist;
expect(fetched.length).to.equal(2);
expect(fetched[0]).to.deep.equal(list[0].object);
lawnchairDao.removeList('type', onRemoveList);
}
function onRemoveList(err) {
expect(err).to.not.exist;
lawnchairDao.list('type', 0, null, onListAgain);
}
function onListAgain(err, fetched) {
expect(err).to.not.exist;
expect(fetched).to.exist;
expect(fetched.length).to.equal(0);
done();
}
});
describe('list', function() {
it('should fail', function(done) {
lawnchairDao.list(undefined, 0, null, function(err) {
expect(err).to.exist;
done();
});
});
});
describe('remove list', function() {
it('should fail', function(done) {
lawnchairDao.removeList(undefined, function(err) {
expect(err).to.exist;
done();
});
});
});
describe('persist/read/remove', function() {
it('should fail', function(done) {
lawnchairDao.persist(undefined, data, function(err) {
expect(err).to.exist;
done();
});
});
it('should fail', function(done) {
lawnchairDao.persist('1234', undefined, function(err) {
expect(err).to.exist;
done();
});
});
it('should work', function(done) {
lawnchairDao.persist(key, data, function(err) {
expect(err).to.not.exist;
lawnchairDao.read(key, onRead);
});
function onRead(err, fetched) {
expect(err).to.not.exist;
expect(fetched).to.deep.equal(data);
lawnchairDao.remove(key, onRemove);
}
function onRemove(err) {
expect(err).to.not.exist;
lawnchairDao.read(key, onReadAgain);
}
function onReadAgain(err, fetched) {
expect(err).to.not.exist;
expect(fetched).to.not.exist;
done();
}
});
});
describe('batch/list/removeList', function() {
it('should fails', function(done) {
lawnchairDao.batch({}, function(err) {
expect(err).to.exist;
done();
});
});
it('should work', function(done) {
var list = [{
key: key,
object: data
}, {
key: key2,
object: data2
}];
lawnchairDao.batch(list, function(err) {
expect(err).to.not.exist;
lawnchairDao.list('type', 0, null, onList);
});
function onList(err, fetched) {
expect(err).to.not.exist;
expect(fetched.length).to.equal(2);
expect(fetched[0]).to.deep.equal(list[0].object);
lawnchairDao.removeList('type', onRemoveList);
}
function onRemoveList(err) {
expect(err).to.not.exist;
lawnchairDao.list('type', 0, null, onListAgain);
}
function onListAgain(err, fetched) {
expect(err).to.not.exist;
expect(fetched).to.exist;
expect(fetched.length).to.equal(0);
done();
}
});
});
});
});

View File

@ -1,236 +1,232 @@
define(function(require) {
'use strict';
'use strict';
var expect = chai.expect,
angular = require('angular'),
mocks = require('angularMocks'),
LoginCtrl = require('js/controller/login'),
EmailDAO = require('js/dao/email-dao'),
Auth = require('js/bo/auth'),
appController = require('js/app-controller'),
KeychainDAO = require('js/dao/keychain-dao');
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('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;
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 || {};
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);
// 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');
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'
});
initStub.yields(null, testKeys);
afterEach(function() {
// restore the browser
if (!hasIdentity) {
delete window.chrome.identity;
}
emailDaoMock.unlock.withArgs({
keypair: testKeys,
passphrase: undefined
}).yields();
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'
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;
done();
});
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;
done();
});
scope = $rootScope.$new();
scope.state = {};
ctrl = $controller(LoginCtrl, {
$location: location,
$scope: scope
});
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'
};
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'
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();
});
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
});
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, {});
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
});
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();
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
});
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'
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();
});
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
});
scope = $rootScope.$new();
scope.state = {};
ctrl = $controller(LoginCtrl, {
$location: location,
$scope: scope
});
});
});

View File

@ -1,116 +1,112 @@
define(function(require) {
'use strict';
'use strict';
var expect = chai.expect,
angular = require('angular'),
Auth = require('js/bo/auth'),
mocks = require('angularMocks'),
LoginExistingCtrl = require('js/controller/login-existing'),
EmailDAO = require('js/dao/email-dao'),
KeychainDAO = require('js/dao/keychain-dao'),
appController = require('js/app-controller');
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');
describe('Login (existing user) Controller unit test', function() {
var scope, location, ctrl, origEmailDao, emailDaoMock,
origAuth, authMock,
emailAddress = 'fred@foo.com',
passphrase = 'asd',
keychainMock;
describe('Login (existing user) Controller unit test', function() {
var scope, location, ctrl, origEmailDao, emailDaoMock,
origAuth, authMock,
emailAddress = 'fred@foo.com',
passphrase = 'asd',
keychainMock;
beforeEach(function() {
// remember original module to restore later
origEmailDao = appController._emailDao;
origAuth = appController._auth;
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);
appController._emailDao = emailDaoMock = sinon.createStubInstance(EmailDAO);
appController._auth = authMock = sinon.createStubInstance(Auth);
keychainMock = sinon.createStubInstance(KeychainDAO);
emailDaoMock._keychain = keychainMock;
keychainMock = sinon.createStubInstance(KeychainDAO);
emailDaoMock._keychain = keychainMock;
emailDaoMock._account = {
emailAddress: emailAddress,
};
emailDaoMock._account = {
emailAddress: emailAddress,
};
angular.module('loginexistingtest', []);
mocks.module('loginexistingtest');
mocks.inject(function($rootScope, $controller, $location) {
location = $location;
scope = $rootScope.$new();
scope.state = {};
ctrl = $controller(LoginExistingCtrl, {
$scope: scope,
$routeParams: {}
});
angular.module('loginexistingtest', []);
mocks.module('loginexistingtest');
mocks.inject(function($rootScope, $controller, $location) {
location = $location;
scope = $rootScope.$new();
scope.state = {};
ctrl = $controller(LoginExistingCtrl, {
$scope: scope,
$routeParams: {}
});
});
});
afterEach(function() {
// restore the module
appController._emailDao = origEmailDao;
appController._auth = origAuth;
afterEach(function() {
// restore the module
appController._emailDao = origEmailDao;
appController._auth = origAuth;
});
describe('initial state', function() {
it('should be well defined', function() {
expect(scope.buttonEnabled).to.be.true;
expect(scope.incorrect).to.be.false;
expect(scope.change).to.exist;
expect(scope.confirmPassphrase).to.exist;
});
});
describe('initial state', function() {
it('should be well defined', function() {
expect(scope.buttonEnabled).to.be.true;
describe('functionality', function() {
describe('change', function() {
it('should set incorrect to false', function() {
scope.incorrect = true;
scope.change();
expect(scope.incorrect).to.be.false;
expect(scope.change).to.exist;
expect(scope.confirmPassphrase).to.exist;
});
});
describe('functionality', function() {
describe('change', function() {
it('should set incorrect to false', function() {
scope.incorrect = true;
describe('confirm passphrase', function() {
it('should unlock crypto and start', function() {
var keypair = {},
pathSpy = sinon.spy(location, 'path');
scope.passphrase = passphrase;
keychainMock.getUserKeyPair.withArgs(emailAddress).yields(null, keypair);
emailDaoMock.unlock.withArgs({
keypair: keypair,
passphrase: passphrase
}).yields();
authMock.storeCredentials.yields();
scope.change();
expect(scope.incorrect).to.be.false;
});
scope.confirmPassphrase();
expect(keychainMock.getUserKeyPair.calledOnce).to.be.true;
expect(emailDaoMock.unlock.calledOnce).to.be.true;
expect(pathSpy.calledOnce).to.be.true;
expect(pathSpy.calledWith('/desktop')).to.be.true;
});
describe('confirm passphrase', function() {
it('should unlock crypto and start', function() {
var keypair = {},
pathSpy = sinon.spy(location, 'path');
scope.passphrase = passphrase;
keychainMock.getUserKeyPair.withArgs(emailAddress).yields(null, keypair);
emailDaoMock.unlock.withArgs({
keypair: keypair,
passphrase: passphrase
}).yields();
authMock.storeCredentials.yields();
it('should not do anything without passphrase', function() {
var pathSpy = sinon.spy(location, 'path');
scope.passphrase = '';
scope.confirmPassphrase();
expect(pathSpy.callCount).to.equal(0);
});
scope.confirmPassphrase();
it('should not work when keypair unavailable', function(done) {
scope.passphrase = passphrase;
keychainMock.getUserKeyPair.withArgs(emailAddress).yields(new Error('asd'));
scope.onError = function(err) {
expect(err.message).to.equal('asd');
expect(keychainMock.getUserKeyPair.calledOnce).to.be.true;
expect(emailDaoMock.unlock.calledOnce).to.be.true;
expect(pathSpy.calledOnce).to.be.true;
expect(pathSpy.calledWith('/desktop')).to.be.true;
});
done();
};
it('should not do anything without passphrase', function() {
var pathSpy = sinon.spy(location, 'path');
scope.passphrase = '';
scope.confirmPassphrase();
expect(pathSpy.callCount).to.equal(0);
});
it('should not work when keypair unavailable', function(done) {
scope.passphrase = passphrase;
keychainMock.getUserKeyPair.withArgs(emailAddress).yields(new Error('asd'));
scope.onError = function(err) {
expect(err.message).to.equal('asd');
expect(keychainMock.getUserKeyPair.calledOnce).to.be.true;
done();
};
scope.confirmPassphrase();
});
scope.confirmPassphrase();
});
});
});

View File

@ -1,202 +1,197 @@
define(function(require) {
'use strict';
'use strict';
var expect = chai.expect,
angular = require('angular'),
Auth = require('js/bo/auth'),
mocks = require('angularMocks'),
LoginInitialCtrl = require('js/controller/login-initial'),
PGP = require('js/crypto/pgp'),
EmailDAO = require('js/dao/email-dao'),
appController = require('js/app-controller');
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');
describe('Login (initial user) Controller unit test', function() {
var scope, ctrl, location, origEmailDao, emailDaoMock,
origAuth, authMock,
emailAddress = 'fred@foo.com',
keyId, expectedKeyId,
cryptoMock;
describe('Login (initial user) Controller unit test', function() {
var scope, ctrl, location, origEmailDao, emailDaoMock,
origAuth, authMock,
emailAddress = 'fred@foo.com',
keyId, expectedKeyId,
cryptoMock;
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);
keyId = '9FEB47936E712926';
expectedKeyId = '6E712926';
cryptoMock = sinon.createStubInstance(PGP);
emailDaoMock._crypto = cryptoMock;
emailDaoMock._account = {
emailAddress: emailAddress,
};
angular.module('logininitialtest', []);
mocks.module('logininitialtest');
mocks.inject(function($rootScope, $controller, $location) {
scope = $rootScope.$new();
location = $location;
scope.state = {
ui: {}
};
ctrl = $controller(LoginInitialCtrl, {
$scope: scope,
$routeParams: {}
});
});
});
afterEach(function() {
// restore the module
appController._emailDao = origEmailDao;
appController._auth = origAuth;
});
describe('initial state', function() {
it('should be well defined', function() {
expect(scope.state.ui).to.equal(1);
});
});
describe('signUpToNewsletter', function() {
var xhrMock, requests;
beforeEach(function() {
// remember original module to restore later
origEmailDao = appController._emailDao;
origAuth = appController._auth;
xhrMock = sinon.useFakeXMLHttpRequest();
requests = [];
appController._emailDao = emailDaoMock = sinon.createStubInstance(EmailDAO);
appController._auth = authMock = sinon.createStubInstance(Auth);
keyId = '9FEB47936E712926';
expectedKeyId = '6E712926';
cryptoMock = sinon.createStubInstance(PGP);
emailDaoMock._crypto = cryptoMock;
emailDaoMock._account = {
emailAddress: emailAddress,
xhrMock.onCreate = function(xhr) {
requests.push(xhr);
};
angular.module('logininitialtest', []);
mocks.module('logininitialtest');
mocks.inject(function($rootScope, $controller, $location) {
scope = $rootScope.$new();
location = $location;
scope.state = {
ui: {}
};
ctrl = $controller(LoginInitialCtrl, {
$scope: scope,
$routeParams: {}
});
});
});
afterEach(function() {
// restore the module
appController._emailDao = origEmailDao;
appController._auth = origAuth;
xhrMock.restore();
});
describe('initial state', function() {
it('should be well defined', function() {
it('should not signup', function() {
scope.state.newsletter = false;
scope.signUpToNewsletter();
expect(requests.length).to.equal(0);
});
it('should fail', function(done) {
scope.state.newsletter = true;
scope.signUpToNewsletter(function(err, xhr) {
expect(err).to.exist;
expect(xhr).to.not.exist;
done();
});
expect(requests.length).to.equal(1);
requests[0].onerror('err');
});
it('should work without callback', function() {
scope.state.newsletter = true;
scope.signUpToNewsletter();
expect(requests.length).to.equal(1);
requests[0].respond(200, {
"Content-Type": "text/plain"
}, 'foobar!');
});
});
describe('go to import key', function() {
var signUpToNewsletterStub;
beforeEach(function() {
signUpToNewsletterStub = sinon.stub(scope, 'signUpToNewsletter');
});
afterEach(function() {
signUpToNewsletterStub.restore();
});
it('should not continue if terms are not accepted', function(done) {
scope.state.agree = undefined;
scope.onError = function(err) {
expect(err.message).to.contain('Terms');
expect(signUpToNewsletterStub.called).to.be.false;
done();
};
scope.importKey();
});
it('should work', function() {
scope.state.agree = true;
scope.importKey();
expect(signUpToNewsletterStub.calledOnce).to.be.true;
expect(location.$$path).to.equal('/login-new-device');
});
});
describe('generate key', function() {
var signUpToNewsletterStub;
beforeEach(function() {
signUpToNewsletterStub = sinon.stub(scope, 'signUpToNewsletter');
});
afterEach(function() {
signUpToNewsletterStub.restore();
});
it('should not continue if terms are not accepted', function(done) {
scope.state.agree = undefined;
scope.onError = function(err) {
expect(err.message).to.contain('Terms');
expect(scope.state.ui).to.equal(1);
});
expect(signUpToNewsletterStub.called).to.be.false;
done();
};
scope.generateKey();
});
describe('signUpToNewsletter', function() {
var xhrMock, requests;
it('should fail due to error in emailDao.unlock', function(done) {
scope.state.agree = true;
beforeEach(function() {
xhrMock = sinon.useFakeXMLHttpRequest();
requests = [];
emailDaoMock.unlock.withArgs({
passphrase: undefined
}).yields(new Error());
authMock.storeCredentials.yields();
xhrMock.onCreate = function(xhr) {
requests.push(xhr);
};
});
scope.onError = function(err) {
expect(err).to.exist;
expect(scope.state.ui).to.equal(1);
expect(signUpToNewsletterStub.called).to.be.true;
done();
};
afterEach(function() {
xhrMock.restore();
});
it('should not signup', function() {
scope.state.newsletter = false;
scope.signUpToNewsletter();
expect(requests.length).to.equal(0);
});
it('should fail', function(done) {
scope.state.newsletter = true;
scope.signUpToNewsletter(function(err, xhr) {
expect(err).to.exist;
expect(xhr).to.not.exist;
done();
});
expect(requests.length).to.equal(1);
requests[0].onerror('err');
});
it('should work without callback', function() {
scope.state.newsletter = true;
scope.signUpToNewsletter();
expect(requests.length).to.equal(1);
requests[0].respond(200, {
"Content-Type": "text/plain"
}, 'foobar!');
});
scope.generateKey();
expect(scope.state.ui).to.equal(2);
});
describe('go to import key', function() {
var signUpToNewsletterStub;
beforeEach(function() {
signUpToNewsletterStub = sinon.stub(scope, 'signUpToNewsletter');
});
afterEach(function() {
signUpToNewsletterStub.restore();
});
it('should unlock crypto', function(done) {
scope.state.agree = true;
it('should not continue if terms are not accepted', function(done) {
scope.state.agree = undefined;
emailDaoMock.unlock.withArgs({
passphrase: undefined
}).yields();
authMock.storeCredentials.yields();
scope.onError = function(err) {
expect(err.message).to.contain('Terms');
expect(signUpToNewsletterStub.called).to.be.false;
done();
};
scope.importKey();
});
it('should work', function() {
scope.state.agree = true;
scope.importKey();
expect(signUpToNewsletterStub.calledOnce).to.be.true;
expect(location.$$path).to.equal('/login-new-device');
});
});
describe('generate key', function() {
var signUpToNewsletterStub;
beforeEach(function() {
signUpToNewsletterStub = sinon.stub(scope, 'signUpToNewsletter');
});
afterEach(function() {
signUpToNewsletterStub.restore();
});
it('should not continue if terms are not accepted', function(done) {
scope.state.agree = undefined;
scope.onError = function(err) {
expect(err.message).to.contain('Terms');
expect(scope.state.ui).to.equal(1);
expect(signUpToNewsletterStub.called).to.be.false;
done();
};
scope.generateKey();
});
it('should fail due to error in emailDao.unlock', function(done) {
scope.state.agree = true;
emailDaoMock.unlock.withArgs({
passphrase: undefined
}).yields(new Error());
authMock.storeCredentials.yields();
scope.onError = function(err) {
expect(err).to.exist;
expect(scope.state.ui).to.equal(1);
expect(signUpToNewsletterStub.called).to.be.true;
done();
};
scope.generateKey();
scope.$apply = function() {
expect(scope.state.ui).to.equal(2);
});
expect(location.$$path).to.equal('/desktop');
expect(emailDaoMock.unlock.calledOnce).to.be.true;
done();
};
it('should unlock crypto', function(done) {
scope.state.agree = true;
emailDaoMock.unlock.withArgs({
passphrase: undefined
}).yields();
authMock.storeCredentials.yields();
scope.$apply = function() {
expect(scope.state.ui).to.equal(2);
expect(location.$$path).to.equal('/desktop');
expect(emailDaoMock.unlock.calledOnce).to.be.true;
done();
};
scope.generateKey();
});
scope.generateKey();
});
});
});

View File

@ -1,190 +1,186 @@
define(function(require) {
'use strict';
'use strict';
var expect = chai.expect,
angular = require('angular'),
mocks = require('angularMocks'),
PGP = require('js/crypto/pgp'),
LoginNewDeviceCtrl = require('js/controller/login-new-device'),
KeychainDAO = require('js/dao/keychain-dao'),
EmailDAO = require('js/dao/email-dao'),
appController = require('js/app-controller');
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');
describe('Login (new device) Controller unit test', function() {
var scope, ctrl, origEmailDao, emailDaoMock, pgpMock,
emailAddress = 'fred@foo.com',
passphrase = 'asd',
keyId,
keychainMock;
describe('Login (new device) Controller unit test', function() {
var scope, ctrl, origEmailDao, emailDaoMock, pgpMock,
emailAddress = 'fred@foo.com',
passphrase = 'asd',
keyId,
keychainMock;
beforeEach(function() {
// remember original module to restore later
origEmailDao = appController._emailDao;
beforeEach(function() {
// remember original module to restore later
origEmailDao = appController._emailDao;
emailDaoMock = sinon.createStubInstance(EmailDAO);
appController._emailDao = emailDaoMock;
emailDaoMock = sinon.createStubInstance(EmailDAO);
appController._emailDao = emailDaoMock;
keyId = '9FEB47936E712926';
emailDaoMock._keychain = keychainMock = sinon.createStubInstance(KeychainDAO);
appController._pgp = pgpMock = sinon.createStubInstance(PGP);
pgpMock.extractPublicKey.returns('publicKeyArmored');
keyId = '9FEB47936E712926';
emailDaoMock._keychain = keychainMock = sinon.createStubInstance(KeychainDAO);
appController._pgp = pgpMock = sinon.createStubInstance(PGP);
pgpMock.extractPublicKey.returns('publicKeyArmored');
emailDaoMock._account = {
emailAddress: emailAddress,
emailDaoMock._account = {
emailAddress: emailAddress,
};
angular.module('loginnewdevicetest', []);
mocks.module('loginnewdevicetest');
mocks.inject(function($rootScope, $controller) {
scope = $rootScope.$new();
scope.state = {
ui: {}
};
angular.module('loginnewdevicetest', []);
mocks.module('loginnewdevicetest');
mocks.inject(function($rootScope, $controller) {
scope = $rootScope.$new();
scope.state = {
ui: {}
};
ctrl = $controller(LoginNewDeviceCtrl, {
$scope: scope,
$routeParams: {}
});
});
});
afterEach(function() {
// restore the module
appController._emailDao = origEmailDao;
});
describe('initial state', function() {
it('should be well defined', function() {
expect(scope.incorrect).to.be.false;
expect(scope.confirmPassphrase).to.exist;
});
});
describe('confirm passphrase', function() {
it('should unlock crypto with a public key on the server', function() {
scope.passphrase = passphrase;
scope.key = {
privateKeyArmored: 'b'
};
pgpMock.getKeyParams.returns({
_id: 'id',
userIds: []
});
keychainMock.getUserKeyPair.withArgs(emailAddress).yields(null, {
_id: keyId,
publicKey: 'a'
});
emailDaoMock.unlock.withArgs(sinon.match.any, passphrase).yields();
keychainMock.putUserKeyPair.yields();
scope.confirmPassphrase();
expect(emailDaoMock.unlock.calledOnce).to.be.true;
expect(keychainMock.getUserKeyPair.calledOnce).to.be.true;
});
it('should unlock crypto with no key on the server', function() {
scope.passphrase = passphrase;
scope.key = {
privateKeyArmored: 'b',
publicKeyArmored: 'a'
};
pgpMock.getKeyParams.returns({
_id: 'id',
userIds: []
});
keychainMock.getUserKeyPair.withArgs(emailAddress).yields();
emailDaoMock.unlock.withArgs(sinon.match.any, passphrase).yields();
keychainMock.putUserKeyPair.yields();
scope.confirmPassphrase();
expect(emailDaoMock.unlock.calledOnce).to.be.true;
expect(keychainMock.getUserKeyPair.calledOnce).to.be.true;
});
it('should not work when keypair upload fails', function(done) {
scope.passphrase = passphrase;
scope.key = {
privateKeyArmored: 'b'
};
pgpMock.getKeyParams.returns({
_id: 'id',
userIds: []
});
keychainMock.getUserKeyPair.withArgs(emailAddress).yields(null, {
_id: keyId,
publicKey: 'a'
});
emailDaoMock.unlock.yields();
keychainMock.putUserKeyPair.yields({
errMsg: 'yo mamma.'
});
scope.onError = function(err) {
expect(err.errMsg).to.equal('yo mamma.');
done();
};
scope.confirmPassphrase();
expect(keychainMock.getUserKeyPair.calledOnce).to.be.true;
expect(emailDaoMock.unlock.calledOnce).to.be.true;
expect(keychainMock.putUserKeyPair.calledOnce).to.be.true;
});
it('should not work when unlock fails', function(done) {
scope.passphrase = passphrase;
scope.key = {
privateKeyArmored: 'b'
};
pgpMock.getKeyParams.returns({
_id: 'id',
userIds: []
});
keychainMock.getUserKeyPair.withArgs(emailAddress).yields(null, {
_id: keyId,
publicKey: 'a'
});
emailDaoMock.unlock.yields({
errMsg: 'yo mamma.'
});
scope.onError = function(err) {
expect(err.errMsg).to.equal('yo mamma.');
done();
};
scope.confirmPassphrase();
expect(scope.incorrect).to.be.true;
expect(keychainMock.getUserKeyPair.calledOnce).to.be.true;
expect(emailDaoMock.unlock.calledOnce).to.be.true;
});
it('should not work when keypair retrieval', function(done) {
scope.passphrase = passphrase;
keychainMock.getUserKeyPair.withArgs(emailAddress).yields({
errMsg: 'yo mamma.'
});
scope.onError = function(err) {
expect(err.errMsg).to.equal('yo mamma.');
done();
};
scope.confirmPassphrase();
expect(keychainMock.getUserKeyPair.calledOnce).to.be.true;
ctrl = $controller(LoginNewDeviceCtrl, {
$scope: scope,
$routeParams: {}
});
});
});
afterEach(function() {
// restore the module
appController._emailDao = origEmailDao;
});
describe('initial state', function() {
it('should be well defined', function() {
expect(scope.incorrect).to.be.false;
expect(scope.confirmPassphrase).to.exist;
});
});
describe('confirm passphrase', function() {
it('should unlock crypto with a public key on the server', function() {
scope.passphrase = passphrase;
scope.key = {
privateKeyArmored: 'b'
};
pgpMock.getKeyParams.returns({
_id: 'id',
userIds: []
});
keychainMock.getUserKeyPair.withArgs(emailAddress).yields(null, {
_id: keyId,
publicKey: 'a'
});
emailDaoMock.unlock.withArgs(sinon.match.any, passphrase).yields();
keychainMock.putUserKeyPair.yields();
scope.confirmPassphrase();
expect(emailDaoMock.unlock.calledOnce).to.be.true;
expect(keychainMock.getUserKeyPair.calledOnce).to.be.true;
});
it('should unlock crypto with no key on the server', function() {
scope.passphrase = passphrase;
scope.key = {
privateKeyArmored: 'b',
publicKeyArmored: 'a'
};
pgpMock.getKeyParams.returns({
_id: 'id',
userIds: []
});
keychainMock.getUserKeyPair.withArgs(emailAddress).yields();
emailDaoMock.unlock.withArgs(sinon.match.any, passphrase).yields();
keychainMock.putUserKeyPair.yields();
scope.confirmPassphrase();
expect(emailDaoMock.unlock.calledOnce).to.be.true;
expect(keychainMock.getUserKeyPair.calledOnce).to.be.true;
});
it('should not work when keypair upload fails', function(done) {
scope.passphrase = passphrase;
scope.key = {
privateKeyArmored: 'b'
};
pgpMock.getKeyParams.returns({
_id: 'id',
userIds: []
});
keychainMock.getUserKeyPair.withArgs(emailAddress).yields(null, {
_id: keyId,
publicKey: 'a'
});
emailDaoMock.unlock.yields();
keychainMock.putUserKeyPair.yields({
errMsg: 'yo mamma.'
});
scope.onError = function(err) {
expect(err.errMsg).to.equal('yo mamma.');
done();
};
scope.confirmPassphrase();
expect(keychainMock.getUserKeyPair.calledOnce).to.be.true;
expect(emailDaoMock.unlock.calledOnce).to.be.true;
expect(keychainMock.putUserKeyPair.calledOnce).to.be.true;
});
it('should not work when unlock fails', function(done) {
scope.passphrase = passphrase;
scope.key = {
privateKeyArmored: 'b'
};
pgpMock.getKeyParams.returns({
_id: 'id',
userIds: []
});
keychainMock.getUserKeyPair.withArgs(emailAddress).yields(null, {
_id: keyId,
publicKey: 'a'
});
emailDaoMock.unlock.yields({
errMsg: 'yo mamma.'
});
scope.onError = function(err) {
expect(err.errMsg).to.equal('yo mamma.');
done();
};
scope.confirmPassphrase();
expect(scope.incorrect).to.be.true;
expect(keychainMock.getUserKeyPair.calledOnce).to.be.true;
expect(emailDaoMock.unlock.calledOnce).to.be.true;
});
it('should not work when keypair retrieval', function(done) {
scope.passphrase = passphrase;
keychainMock.getUserKeyPair.withArgs(emailAddress).yields({
errMsg: 'yo mamma.'
});
scope.onError = function(err) {
expect(err.errMsg).to.equal('yo mamma.');
done();
};
scope.confirmPassphrase();
expect(keychainMock.getUserKeyPair.calledOnce).to.be.true;
});
});
});

View File

@ -1,39 +1,250 @@
define(function(require) {
'use strict';
'use strict';
var expect = chai.expect,
angular = require('angular'),
mocks = require('angularMocks'),
Auth = require('js/bo/auth'),
LoginPrivateKeyDownloadCtrl = require('js/controller/login-privatekey-download'),
EmailDAO = require('js/dao/email-dao'),
appController = require('js/app-controller'),
KeychainDAO = require('js/dao/keychain-dao');
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');
describe('Login Private Key Download Controller unit test', function() {
var scope, location, ctrl,
origEmailDao, emailDaoMock,
origAuth, authMock,
origKeychain, keychainMock,
emailAddress = 'fred@foo.com';
describe('Login Private Key Download Controller unit test', function() {
var scope, location, ctrl,
origEmailDao, emailDaoMock,
origAuth, authMock,
origKeychain, 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;
beforeEach(function(done) {
// 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 = authMock = sinon.createStubInstance(Auth);
appController._emailDao = emailDaoMock = sinon.createStubInstance(EmailDAO);
appController._keychain = keychainMock = sinon.createStubInstance(KeychainDAO);
appController._auth = authMock = sinon.createStubInstance(Auth);
emailDaoMock._account = {
emailAddress: emailAddress
emailDaoMock._account = {
emailAddress: emailAddress
};
angular.module('login-privatekey-download-test', []);
mocks.module('login-privatekey-download-test');
mocks.inject(function($controller, $rootScope) {
scope = $rootScope.$new();
scope.state = {};
ctrl = $controller(LoginPrivateKeyDownloadCtrl, {
$location: location,
$scope: scope,
$routeParams: {}
});
done();
});
});
afterEach(function() {
// restore the app controller module
appController._emailDao = origEmailDao;
appController._keychain = origKeychain;
appController._auth = origAuth;
});
describe('initialization', function() {
it('should work', function() {
expect(scope.step).to.equal(1);
});
});
describe('verifyRecoveryToken', function() {
var testKeypair = {
publicKey: {
_id: 'id'
}
};
it('should fail for empty recovery token', function(done) {
scope.onError = function(err) {
expect(err).to.exist;
done();
};
angular.module('login-privatekey-download-test', []);
mocks.module('login-privatekey-download-test');
mocks.inject(function($controller, $rootScope) {
scope.recoveryToken = undefined;
scope.verifyRecoveryToken();
});
it('should fail in keychain.getUserKeyPair', function(done) {
keychainMock.getUserKeyPair.yields(42);
scope.onError = function(err) {
expect(err).to.exist;
expect(keychainMock.getUserKeyPair.calledOnce).to.be.true;
done();
};
scope.recoveryToken = 'token';
scope.verifyRecoveryToken();
});
it('should fail in keychain.downloadPrivateKey', function(done) {
keychainMock.getUserKeyPair.yields(null, testKeypair);
keychainMock.downloadPrivateKey.yields(42);
scope.onError = function(err) {
expect(err).to.exist;
expect(keychainMock.getUserKeyPair.calledOnce).to.be.true;
expect(keychainMock.downloadPrivateKey.calledOnce).to.be.true;
done();
};
scope.recoveryToken = 'token';
scope.verifyRecoveryToken();
});
it('should work', function(done) {
keychainMock.getUserKeyPair.yields(null, testKeypair);
keychainMock.downloadPrivateKey.yields(null, 'encryptedPrivateKey');
scope.recoveryToken = 'token';
scope.verifyRecoveryToken(function() {
expect(scope.encryptedPrivateKey).to.equal('encryptedPrivateKey');
done();
});
});
});
describe('handlePaste', function() {
it('should work', function() {
scope.handlePaste({
clipboardData: {
getData: function(val) {
expect(val).to.equal('text/plain');
return '1qaz-2wsx-3edc-4rfv-5tgb-6yhn';
}
}
});
expect(scope.code0).to.equal('1qaz');
expect(scope.code1).to.equal('2wsx');
expect(scope.code2).to.equal('3edc');
expect(scope.code3).to.equal('4rfv');
expect(scope.code4).to.equal('5tgb');
expect(scope.code5).to.equal('6yhn');
});
});
describe('decryptAndStorePrivateKeyLocally', function() {
beforeEach(function() {
scope.code0 = '0';
scope.code1 = '1';
scope.code2 = '2';
scope.code3 = '3';
scope.code4 = '4';
scope.code5 = '5';
scope.encryptedPrivateKey = {
encryptedPrivateKey: 'encryptedPrivateKey'
};
scope.cachedKeypair = {
publicKey: {
_id: 'keyId'
}
};
});
it('should fail on empty code', function(done) {
scope.code0 = '';
scope.code1 = '';
scope.code2 = '';
scope.code3 = '';
scope.code4 = '';
scope.code5 = '';
scope.onError = function(err) {
expect(err).to.exist;
done();
};
scope.decryptAndStorePrivateKeyLocally();
});
it('should fail on decryptAndStorePrivateKeyLocally', function(done) {
keychainMock.decryptAndStorePrivateKeyLocally.yields(42);
scope.onError = function(err) {
expect(err).to.exist;
expect(keychainMock.decryptAndStorePrivateKeyLocally.calledOnce).to.be.true;
done();
};
scope.decryptAndStorePrivateKeyLocally();
});
it('should goto /login-existing on emailDao.unlock fail', function(done) {
keychainMock.decryptAndStorePrivateKeyLocally.yields(null, {
encryptedKey: 'keyArmored'
});
emailDaoMock.unlock.yields(42);
scope.goTo = function(location) {
expect(location).to.equal('/login-existing');
expect(keychainMock.decryptAndStorePrivateKeyLocally.calledOnce).to.be.true;
expect(emailDaoMock.unlock.calledOnce).to.be.true;
done();
};
scope.decryptAndStorePrivateKeyLocally();
});
it('should goto /desktop on emailDao.unlock success', function(done) {
keychainMock.decryptAndStorePrivateKeyLocally.yields(null, {
encryptedKey: 'keyArmored'
});
emailDaoMock.unlock.yields();
authMock.storeCredentials.yields();
scope.goTo = function(location) {
expect(location).to.equal('/desktop');
expect(keychainMock.decryptAndStorePrivateKeyLocally.calledOnce).to.be.true;
expect(emailDaoMock.unlock.calledOnce).to.be.true;
done();
};
scope.decryptAndStorePrivateKeyLocally();
});
});
describe('goForward', function() {
it('should work in step 1', function() {
var verifyRecoveryTokenStub = sinon.stub(scope, 'verifyRecoveryToken');
verifyRecoveryTokenStub.yields();
scope.step = 1;
scope.goForward();
expect(verifyRecoveryTokenStub.calledOnce).to.be.true;
expect(scope.step).to.equal(2);
verifyRecoveryTokenStub.restore();
});
it('should work in step 2', function() {
var decryptAndStorePrivateKeyLocallyStub = sinon.stub(scope, 'decryptAndStorePrivateKeyLocally');
decryptAndStorePrivateKeyLocallyStub.returns();
scope.step = 2;
scope.goForward();
expect(decryptAndStorePrivateKeyLocallyStub.calledOnce).to.be.true;
decryptAndStorePrivateKeyLocallyStub.restore();
});
});
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, {
@ -41,224 +252,9 @@ define(function(require) {
$scope: scope,
$routeParams: {}
});
done();
});
});
afterEach(function() {
// restore the app controller module
appController._emailDao = origEmailDao;
appController._keychain = origKeychain;
appController._auth = origAuth;
});
describe('initialization', function() {
it('should work', function() {
expect(scope.step).to.equal(1);
});
});
describe('verifyRecoveryToken', function() {
var testKeypair = {
publicKey: {
_id: 'id'
}
};
it('should fail for empty recovery token', function(done) {
scope.onError = function(err) {
expect(err).to.exist;
done();
};
scope.recoveryToken = undefined;
scope.verifyRecoveryToken();
});
it('should fail in keychain.getUserKeyPair', function(done) {
keychainMock.getUserKeyPair.yields(42);
scope.onError = function(err) {
expect(err).to.exist;
expect(keychainMock.getUserKeyPair.calledOnce).to.be.true;
done();
};
scope.recoveryToken = 'token';
scope.verifyRecoveryToken();
});
it('should fail in keychain.downloadPrivateKey', function(done) {
keychainMock.getUserKeyPair.yields(null, testKeypair);
keychainMock.downloadPrivateKey.yields(42);
scope.onError = function(err) {
expect(err).to.exist;
expect(keychainMock.getUserKeyPair.calledOnce).to.be.true;
expect(keychainMock.downloadPrivateKey.calledOnce).to.be.true;
done();
};
scope.recoveryToken = 'token';
scope.verifyRecoveryToken();
});
it('should work', function(done) {
keychainMock.getUserKeyPair.yields(null, testKeypair);
keychainMock.downloadPrivateKey.yields(null, 'encryptedPrivateKey');
scope.recoveryToken = 'token';
scope.verifyRecoveryToken(function() {
expect(scope.encryptedPrivateKey).to.equal('encryptedPrivateKey');
done();
});
});
});
describe('handlePaste', function() {
it('should work', function() {
scope.handlePaste({
clipboardData: {
getData: function(val) {
expect(val).to.equal('text/plain');
return '1qaz-2wsx-3edc-4rfv-5tgb-6yhn';
}
}
});
expect(scope.code0).to.equal('1qaz');
expect(scope.code1).to.equal('2wsx');
expect(scope.code2).to.equal('3edc');
expect(scope.code3).to.equal('4rfv');
expect(scope.code4).to.equal('5tgb');
expect(scope.code5).to.equal('6yhn');
});
});
describe('decryptAndStorePrivateKeyLocally', function() {
beforeEach(function() {
scope.code0 = '0';
scope.code1 = '1';
scope.code2 = '2';
scope.code3 = '3';
scope.code4 = '4';
scope.code5 = '5';
scope.encryptedPrivateKey = {
encryptedPrivateKey: 'encryptedPrivateKey'
};
scope.cachedKeypair = {
publicKey: {
_id: 'keyId'
}
};
});
it('should fail on empty code', function(done) {
scope.code0 = '';
scope.code1 = '';
scope.code2 = '';
scope.code3 = '';
scope.code4 = '';
scope.code5 = '';
scope.onError = function(err) {
expect(err).to.exist;
done();
};
scope.decryptAndStorePrivateKeyLocally();
});
it('should fail on decryptAndStorePrivateKeyLocally', function(done) {
keychainMock.decryptAndStorePrivateKeyLocally.yields(42);
scope.onError = function(err) {
expect(err).to.exist;
expect(keychainMock.decryptAndStorePrivateKeyLocally.calledOnce).to.be.true;
done();
};
scope.decryptAndStorePrivateKeyLocally();
});
it('should goto /login-existing on emailDao.unlock fail', function(done) {
keychainMock.decryptAndStorePrivateKeyLocally.yields(null, {
encryptedKey: 'keyArmored'
});
emailDaoMock.unlock.yields(42);
scope.goTo = function(location) {
expect(location).to.equal('/login-existing');
expect(keychainMock.decryptAndStorePrivateKeyLocally.calledOnce).to.be.true;
expect(emailDaoMock.unlock.calledOnce).to.be.true;
done();
};
scope.decryptAndStorePrivateKeyLocally();
});
it('should goto /desktop on emailDao.unlock success', function(done) {
keychainMock.decryptAndStorePrivateKeyLocally.yields(null, {
encryptedKey: 'keyArmored'
});
emailDaoMock.unlock.yields();
authMock.storeCredentials.yields();
scope.goTo = function(location) {
expect(location).to.equal('/desktop');
expect(keychainMock.decryptAndStorePrivateKeyLocally.calledOnce).to.be.true;
expect(emailDaoMock.unlock.calledOnce).to.be.true;
done();
};
scope.decryptAndStorePrivateKeyLocally();
});
});
describe('goForward', function() {
it('should work in step 1', function() {
var verifyRecoveryTokenStub = sinon.stub(scope, 'verifyRecoveryToken');
verifyRecoveryTokenStub.yields();
scope.step = 1;
scope.goForward();
expect(verifyRecoveryTokenStub.calledOnce).to.be.true;
expect(scope.step).to.equal(2);
verifyRecoveryTokenStub.restore();
});
it('should work in step 2', function() {
var decryptAndStorePrivateKeyLocallyStub = sinon.stub(scope, 'decryptAndStorePrivateKeyLocally');
decryptAndStorePrivateKeyLocallyStub.returns();
scope.step = 2;
scope.goForward();
expect(decryptAndStorePrivateKeyLocallyStub.calledOnce).to.be.true;
decryptAndStorePrivateKeyLocallyStub.restore();
});
});
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: {}
});
});
scope.goTo('/desktop');
});
scope.goTo('/desktop');
});
});
});

View File

@ -1,101 +1,97 @@
define(function(require) {
'use strict';
'use strict';
var expect = chai.expect,
angular = require('angular'),
mocks = require('angularMocks'),
Auth = require('js/bo/auth'),
ConnectionDoctor = require('js/util/connection-doctor'),
SetCredentialsCtrl = require('js/controller/login-set-credentials'),
appController = require('js/app-controller');
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');
describe('Login (Set Credentials) Controller unit test', function() {
// Angular parameters
var scope, location, provider;
describe('Login (Set Credentials) Controller unit test', function() {
// Angular parameters
var scope, location, provider;
// Stubs
var auth, origAuth, doctor, origDoctor;
// Stubs
var auth, origAuth, doctor, origDoctor;
// SUT
var setCredentialsCtrl;
// 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);
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);
// setup the controller
angular.module('setcredentialstest', []);
mocks.module('setcredentialstest');
mocks.inject(function($rootScope, $controller, $location) {
scope = $rootScope.$new();
location = $location;
location.search({
provider: provider
});
scope.state = {};
setCredentialsCtrl = $controller(SetCredentialsCtrl, {
$scope: scope,
$routeParams: {}
});
// setup the controller
angular.module('setcredentialstest', []);
mocks.module('setcredentialstest');
mocks.inject(function($rootScope, $controller, $location) {
scope = $rootScope.$new();
location = $location;
location.search({
provider: provider
});
});
afterEach(function() {
// restore pre-test state
appController._auth = origAuth;
appController._doctor = origDoctor;
});
describe('set credentials', function() {
it('should work', function() {
scope.emailAddress = 'emailemailemailemail';
scope.password = 'passwdpasswdpasswdpasswd';
scope.smtpHost = 'hosthosthost';
scope.smtpPort = 1337;
scope.smtpEncryption = '1'; // STARTTLS
scope.imapHost = 'hosthosthost';
scope.imapPort = 1337;
scope.imapEncryption = '2'; // TLS
scope.realname = 'peter pan';
var expectedCredentials = {
provider: provider,
emailAddress: scope.emailAddress,
username: scope.username || scope.emailAddress,
realname: scope.realname,
password: scope.password,
xoauth2: undefined,
imap: {
host: scope.imapHost.toLowerCase(),
port: scope.imapPort,
secure: true,
ignoreTLS: false,
ca: undefined,
pinned: false
},
smtp: {
host: scope.smtpHost.toLowerCase(),
port: scope.smtpPort,
secure: false,
ignoreTLS: false,
ca: undefined,
pinned: false
}
};
doctor.check.yields(); // synchronous yields!
scope.test();
expect(doctor.check.calledOnce).to.be.true;
expect(doctor.configure.calledOnce).to.be.true;
expect(doctor.configure.calledWith(expectedCredentials)).to.be.true;
expect(auth.setCredentials.calledOnce).to.be.true;
scope.state = {};
setCredentialsCtrl = $controller(SetCredentialsCtrl, {
$scope: scope,
$routeParams: {}
});
});
});
afterEach(function() {
// restore pre-test state
appController._auth = origAuth;
appController._doctor = origDoctor;
});
describe('set credentials', function() {
it('should work', function() {
scope.emailAddress = 'emailemailemailemail';
scope.password = 'passwdpasswdpasswdpasswd';
scope.smtpHost = 'hosthosthost';
scope.smtpPort = 1337;
scope.smtpEncryption = '1'; // STARTTLS
scope.imapHost = 'hosthosthost';
scope.imapPort = 1337;
scope.imapEncryption = '2'; // TLS
scope.realname = 'peter pan';
var expectedCredentials = {
provider: provider,
emailAddress: scope.emailAddress,
username: scope.username || scope.emailAddress,
realname: scope.realname,
password: scope.password,
xoauth2: undefined,
imap: {
host: scope.imapHost.toLowerCase(),
port: scope.imapPort,
secure: true,
ignoreTLS: false,
ca: undefined,
pinned: false
},
smtp: {
host: scope.smtpHost.toLowerCase(),
port: scope.smtpPort,
secure: false,
ignoreTLS: false,
ca: undefined,
pinned: false
}
};
doctor.check.yields(); // synchronous yields!
scope.test();
expect(doctor.check.calledOnce).to.be.true;
expect(doctor.configure.calledOnce).to.be.true;
expect(doctor.configure.calledWith(expectedCredentials)).to.be.true;
expect(auth.setCredentials.calledOnce).to.be.true;
});
});
});

View File

@ -1,451 +1,447 @@
define(function(require) {
'use strict';
'use strict';
var expect = chai.expect,
angular = require('angular'),
mocks = require('angularMocks'),
MailListCtrl = require('js/controller/mail-list'),
EmailDAO = require('js/dao/email-dao'),
DeviceStorageDAO = require('js/dao/devicestorage-dao'),
KeychainDAO = require('js/dao/keychain-dao'),
appController = require('js/app-controller'),
notification = require('js/util/notification');
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.Assertion.includeStack = true;
chai.Assertion.includeStack = true;
describe('Mail List controller unit test', function() {
var scope, ctrl, origEmailDao, emailDaoMock, keychainMock, deviceStorageMock,
emailAddress, emails,
hasChrome, hasSocket, hasRuntime, hasIdentity;
describe('Mail List controller unit test', function() {
var scope, ctrl, origEmailDao, emailDaoMock, keychainMock, deviceStorageMock,
emailAddress, emails,
hasChrome, hasSocket, hasRuntime, hasIdentity;
beforeEach(function() {
hasChrome = !!window.chrome;
hasSocket = !!window.chrome.socket;
hasIdentity = !!window.chrome.identity;
if (!hasChrome) {
window.chrome = {};
}
if (!hasSocket) {
window.chrome.socket = {};
}
if (!hasRuntime) {
window.chrome.runtime = {
getURL: function() {}
};
}
if (!hasIdentity) {
window.chrome.identity = {};
}
beforeEach(function() {
hasChrome = !!window.chrome;
hasSocket = !!window.chrome.socket;
hasIdentity = !!window.chrome.identity;
if (!hasChrome) {
window.chrome = {};
}
if (!hasSocket) {
window.chrome.socket = {};
}
if (!hasRuntime) {
window.chrome.runtime = {
getURL: function() {}
};
}
if (!hasIdentity) {
window.chrome.identity = {};
}
emails = [{
unread: true
}, {
unread: true
}, {
unread: true
}];
appController._outboxBo = {
pendingEmails: emails
emails = [{
unread: true
}, {
unread: true
}, {
unread: true
}];
appController._outboxBo = {
pendingEmails: emails
};
origEmailDao = appController._emailDao;
emailDaoMock = sinon.createStubInstance(EmailDAO);
appController._emailDao = emailDaoMock;
emailAddress = 'fred@foo.com';
emailDaoMock._account = {
emailAddress: emailAddress,
};
keychainMock = sinon.createStubInstance(KeychainDAO);
appController._keychain = keychainMock;
deviceStorageMock = sinon.createStubInstance(DeviceStorageDAO);
emailDaoMock._devicestorage = deviceStorageMock;
angular.module('maillisttest', []);
mocks.module('maillisttest');
mocks.inject(function($rootScope, $controller) {
scope = $rootScope.$new();
scope.state = {
read: {
toggle: function() {}
}
};
origEmailDao = appController._emailDao;
emailDaoMock = sinon.createStubInstance(EmailDAO);
appController._emailDao = emailDaoMock;
emailAddress = 'fred@foo.com';
emailDaoMock._account = {
emailAddress: emailAddress,
};
keychainMock = sinon.createStubInstance(KeychainDAO);
appController._keychain = keychainMock;
deviceStorageMock = sinon.createStubInstance(DeviceStorageDAO);
emailDaoMock._devicestorage = deviceStorageMock;
angular.module('maillisttest', []);
mocks.module('maillisttest');
mocks.inject(function($rootScope, $controller) {
scope = $rootScope.$new();
scope.state = {
read: {
toggle: function() {}
}
};
scope.loadVisibleBodies = function() {};
ctrl = $controller(MailListCtrl, {
$scope: scope,
$routeParams: {}
});
});
});
afterEach(function() {
if (!hasSocket) {
delete window.chrome.socket;
}
if (!hasRuntime) {
delete window.chrome.runtime;
}
if (!hasChrome) {
delete window.chrome;
}
if (!hasIdentity) {
delete window.chrome.identity;
}
// restore the module
appController._emailDao = origEmailDao;
});
describe('displayMore', function() {
beforeEach(function() {
scope.state.nav = {
currentFolder: {
messages: ['a', 'b']
}
};
});
it('should not do anything when display length equals messages length', function() {
scope.displayMessages = ['a', 'b'];
scope.displayMore();
expect(scope.displayMessages.length).to.equal(scope.state.nav.currentFolder.messages.length);
});
it('should append next message interval', function() {
scope.displayMessages = ['a'];
scope.displayMore();
expect(scope.displayMessages.length).to.equal(scope.state.nav.currentFolder.messages.length);
});
});
describe('displaySearchResults', function() {
var clock;
beforeEach(function() {
scope.state.nav = {
currentFolder: {
messages: ['a', 'b']
}
};
scope.watchMessages();
scope.watchOnline();
clock = sinon.useFakeTimers();
});
afterEach(function() {
clock.restore();
});
it('should show initial message on empty', function() {
scope.displaySearchResults();
expect(scope.searching).to.be.false;
expect(scope.lastUpdateLbl).to.equal('Online');
expect(scope.displayMessages.length).to.equal(2);
});
it('should show initial message on empty', function() {
var searchStub = sinon.stub(scope, 'search');
searchStub.returns(['a']);
scope.displaySearchResults('query');
expect(scope.searching).to.be.true;
expect(scope.lastUpdateLbl).to.equal('Searching ...');
clock.tick(500);
expect(scope.displayMessages).to.deep.equal(['a']);
expect(scope.searching).to.be.false;
expect(scope.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);
});
});
describe('scope variables', function() {
it('should be set correctly', function() {
expect(scope.select).to.exist;
expect(scope.remove).to.exist;
expect(scope.state.mailList).to.exist;
});
});
describe('push notification', function() {
beforeEach(function() {
scope._stopWatchTask();
});
afterEach(function() {
notification.create.restore();
});
it('should succeed for single mail', function(done) {
var mail = {
uid: 123,
from: [{
address: 'asd'
}],
subject: 'this is the subject!',
unread: true
};
sinon.stub(notification, 'create', function(opts) {
expect(opts.title).to.equal(mail.from[0].address);
expect(opts.message).to.equal(mail.subject);
opts.onClick();
expect(scope.state.mailList.selected).to.equal(mail);
done();
});
scope.state.nav = {
currentFolder: {
type: 'asd',
messages: [mail]
}
};
emailDaoMock.onIncomingMessage([mail]);
});
it('should succeed for multiple mails', function(done) {
var mails = [{
uid: 1,
from: [{
address: 'asd'
}],
subject: 'this is the subject!',
unread: true
}, {
uid: 2,
from: [{
address: 'qwe'
}],
subject: 'this is the other subject!',
unread: true
}, {
uid: 3,
from: [{
address: 'qwe'
}],
subject: 'this is the other subject!',
unread: false
}];
sinon.stub(notification, 'create', function(opts) {
expect(opts.title).to.equal('2 new messages');
expect(opts.message).to.equal(mails[0].subject + '\n' + mails[1].subject);
opts.onClick();
expect(scope.state.mailList.selected).to.equal(mails[0]);
done();
});
scope.state.nav = {
currentFolder: {
type: 'asd',
messages: mails
}
};
emailDaoMock.onIncomingMessage(mails);
});
});
describe('getBody', function() {
it('should get the mail content', function() {
scope.state.nav = {
currentFolder: {
type: 'asd',
}
};
scope.getBody();
expect(emailDaoMock.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');
var mail = {
from: [{
address: 'asd'
}],
unread: true,
};
scope.state = {
nav: {
currentFolder: {
type: 'Inbox'
}
},
mailList: {},
read: {
toggle: function() {}
}
};
keychainMock.refreshKeyForUserId.withArgs(mail.from[0].address).yields();
scope.select(mail);
expect(emailDaoMock.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;
notification.close.restore();
});
it('should decrypt and focus a read mail', function() {
var mail = {
from: [{
address: 'asd'
}],
unread: false
};
scope.state = {
mailList: {},
read: {
toggle: function() {}
},
nav: {
currentFolder: {
type: 'asd'
}
}
};
keychainMock.refreshKeyForUserId.withArgs(mail.from[0].address).yields();
scope.select(mail);
expect(emailDaoMock.decryptBody.calledOnce).to.be.true;
expect(keychainMock.refreshKeyForUserId.calledOnce).to.be.true;
expect(scope.state.mailList.selected).to.equal(mail);
});
});
describe('remove', function() {
it('should not delete without a selected mail', function() {
scope.remove();
});
it('should delete the selected mail', function() {
var uid, mail, currentFolder;
scope._stopWatchTask();
scope.account = {};
uid = 123;
mail = {
uid: uid,
from: [{
address: 'asd'
}],
subject: '[whiteout] asdasd',
unread: true
};
currentFolder = {
type: 'Inbox',
path: 'INBOX',
messages: [mail]
};
scope.account.folders = [currentFolder];
scope.state.nav = {
currentFolder: currentFolder
};
emailDaoMock.deleteMessage.yields();
scope.remove(mail);
expect(emailDaoMock.deleteMessage.calledOnce).to.be.true;
expect(scope.state.mailList.selected).to.exist;
scope.loadVisibleBodies = function() {};
ctrl = $controller(MailListCtrl, {
$scope: scope,
$routeParams: {}
});
});
});
afterEach(function() {
if (!hasSocket) {
delete window.chrome.socket;
}
if (!hasRuntime) {
delete window.chrome.runtime;
}
if (!hasChrome) {
delete window.chrome;
}
if (!hasIdentity) {
delete window.chrome.identity;
}
// restore the module
appController._emailDao = origEmailDao;
});
describe('displayMore', function() {
beforeEach(function() {
scope.state.nav = {
currentFolder: {
messages: ['a', 'b']
}
};
});
it('should not do anything when display length equals messages length', function() {
scope.displayMessages = ['a', 'b'];
scope.displayMore();
expect(scope.displayMessages.length).to.equal(scope.state.nav.currentFolder.messages.length);
});
it('should append next message interval', function() {
scope.displayMessages = ['a'];
scope.displayMore();
expect(scope.displayMessages.length).to.equal(scope.state.nav.currentFolder.messages.length);
});
});
describe('displaySearchResults', function() {
var clock;
beforeEach(function() {
scope.state.nav = {
currentFolder: {
messages: ['a', 'b']
}
};
scope.watchMessages();
scope.watchOnline();
clock = sinon.useFakeTimers();
});
afterEach(function() {
clock.restore();
});
it('should show initial message on empty', function() {
scope.displaySearchResults();
expect(scope.searching).to.be.false;
expect(scope.lastUpdateLbl).to.equal('Online');
expect(scope.displayMessages.length).to.equal(2);
});
it('should show initial message on empty', function() {
var searchStub = sinon.stub(scope, 'search');
searchStub.returns(['a']);
scope.displaySearchResults('query');
expect(scope.searching).to.be.true;
expect(scope.lastUpdateLbl).to.equal('Searching ...');
clock.tick(500);
expect(scope.displayMessages).to.deep.equal(['a']);
expect(scope.searching).to.be.false;
expect(scope.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);
});
});
describe('scope variables', function() {
it('should be set correctly', function() {
expect(scope.select).to.exist;
expect(scope.remove).to.exist;
expect(scope.state.mailList).to.exist;
});
});
describe('push notification', function() {
beforeEach(function() {
scope._stopWatchTask();
});
afterEach(function() {
notification.create.restore();
});
it('should succeed for single mail', function(done) {
var mail = {
uid: 123,
from: [{
address: 'asd'
}],
subject: 'this is the subject!',
unread: true
};
sinon.stub(notification, 'create', function(opts) {
expect(opts.title).to.equal(mail.from[0].address);
expect(opts.message).to.equal(mail.subject);
opts.onClick();
expect(scope.state.mailList.selected).to.equal(mail);
done();
});
scope.state.nav = {
currentFolder: {
type: 'asd',
messages: [mail]
}
};
emailDaoMock.onIncomingMessage([mail]);
});
it('should succeed for multiple mails', function(done) {
var mails = [{
uid: 1,
from: [{
address: 'asd'
}],
subject: 'this is the subject!',
unread: true
}, {
uid: 2,
from: [{
address: 'qwe'
}],
subject: 'this is the other subject!',
unread: true
}, {
uid: 3,
from: [{
address: 'qwe'
}],
subject: 'this is the other subject!',
unread: false
}];
sinon.stub(notification, 'create', function(opts) {
expect(opts.title).to.equal('2 new messages');
expect(opts.message).to.equal(mails[0].subject + '\n' + mails[1].subject);
opts.onClick();
expect(scope.state.mailList.selected).to.equal(mails[0]);
done();
});
scope.state.nav = {
currentFolder: {
type: 'asd',
messages: mails
}
};
emailDaoMock.onIncomingMessage(mails);
});
});
describe('getBody', function() {
it('should get the mail content', function() {
scope.state.nav = {
currentFolder: {
type: 'asd',
}
};
scope.getBody();
expect(emailDaoMock.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');
var mail = {
from: [{
address: 'asd'
}],
unread: true,
};
scope.state = {
nav: {
currentFolder: {
type: 'Inbox'
}
},
mailList: {},
read: {
toggle: function() {}
}
};
keychainMock.refreshKeyForUserId.withArgs(mail.from[0].address).yields();
scope.select(mail);
expect(emailDaoMock.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;
notification.close.restore();
});
it('should decrypt and focus a read mail', function() {
var mail = {
from: [{
address: 'asd'
}],
unread: false
};
scope.state = {
mailList: {},
read: {
toggle: function() {}
},
nav: {
currentFolder: {
type: 'asd'
}
}
};
keychainMock.refreshKeyForUserId.withArgs(mail.from[0].address).yields();
scope.select(mail);
expect(emailDaoMock.decryptBody.calledOnce).to.be.true;
expect(keychainMock.refreshKeyForUserId.calledOnce).to.be.true;
expect(scope.state.mailList.selected).to.equal(mail);
});
});
describe('remove', function() {
it('should not delete without a selected mail', function() {
scope.remove();
});
it('should delete the selected mail', function() {
var uid, mail, currentFolder;
scope._stopWatchTask();
scope.account = {};
uid = 123;
mail = {
uid: uid,
from: [{
address: 'asd'
}],
subject: '[whiteout] asdasd',
unread: true
};
currentFolder = {
type: 'Inbox',
path: 'INBOX',
messages: [mail]
};
scope.account.folders = [currentFolder];
scope.state.nav = {
currentFolder: currentFolder
};
emailDaoMock.deleteMessage.yields();
scope.remove(mail);
expect(emailDaoMock.deleteMessage.calledOnce).to.be.true;
expect(scope.state.mailList.selected).to.exist;
});
});
});

View File

@ -1,113 +0,0 @@
'use strict';
// Mozilla bind polyfill because phantomjs is stupid
if (!Function.prototype.bind) {
Function.prototype.bind = function(oThis) {
if (typeof this !== "function") {
// closest thing possible to the ECMAScript 5 internal IsCallable function
throw new TypeError("Function.prototype.bind - what is trying to be bound is not callable");
}
var aArgs = Array.prototype.slice.call(arguments, 1),
fToBind = this,
FNOP = function() {},
fBound = function() {
return fToBind.apply(this instanceof FNOP && oThis ? this : oThis, aArgs.concat(Array.prototype.slice.call(arguments)));
};
FNOP.prototype = this.prototype;
fBound.prototype = new FNOP();
return fBound;
};
}
// a warm round of applause for phantomjs for missing events
(function() {
function CustomEvent(event, params) {
params = params || {
bubbles: false,
cancelable: false,
detail: undefined
};
var evt = document.createEvent('CustomEvent');
evt.initCustomEvent(event, params.bubbles, params.cancelable, params.detail);
return evt;
}
CustomEvent.prototype = window.Event.prototype;
window.CustomEvent = CustomEvent;
})();
require(['../../src/require-config'], function() {
require.config({
baseUrl: '../../src/lib',
paths: {
angularMocks: '../../test/lib/angular-mocks'
},
shim: {
angularMocks: {
exports: 'angular.mock',
deps: ['angular']
}
}
});
// Start the main app logic.
require(['js/app-config', 'axe'], function(app, axe) {
app.config.workerPath = '../../src/js';
// turn off logging in the test
axe.removeAppender(axe.defaultAppender);
startTests();
});
});
function startTests() {
mocha.setup('bdd');
require(
[
'test/unit/oauth-test',
'test/unit/auth-test',
'test/unit/email-dao-test',
'test/unit/app-controller-test',
'test/unit/pgp-test',
'test/unit/crypto-test',
'test/unit/backbutton-handler-test',
'test/unit/rest-dao-test',
'test/unit/admin-dao-test',
'test/unit/publickey-dao-test',
'test/unit/privatekey-dao-test',
'test/unit/lawnchair-dao-test',
'test/unit/keychain-dao-test',
'test/unit/devicestorage-dao-test',
'test/unit/dialog-ctrl-test',
'test/unit/add-account-ctrl-test',
'test/unit/account-ctrl-test',
'test/unit/set-passphrase-ctrl-test',
'test/unit/contacts-ctrl-test',
'test/unit/login-existing-ctrl-test',
'test/unit/login-initial-ctrl-test',
'test/unit/login-new-device-ctrl-test',
'test/unit/login-privatekey-download-ctrl-test',
'test/unit/login-set-credentials-ctrl-test',
'test/unit/privatekey-upload-ctrl-test',
'test/unit/login-ctrl-test',
'test/unit/read-ctrl-test',
'test/unit/navigation-ctrl-test',
'test/unit/mail-list-ctrl-test',
'test/unit/write-ctrl-test',
'test/unit/outbox-bo-test',
'test/unit/invitation-dao-test',
'test/unit/update-handler-test',
'test/unit/connection-doctor-test'
], function() {
//Tests loaded, run tests
mocha.run();
}
);
}

View File

@ -1,101 +1,97 @@
define(function(require) {
'use strict';
'use strict';
var expect = chai.expect,
angular = require('angular'),
mocks = require('angularMocks'),
NavigationCtrl = require('js/controller/navigation'),
EmailDAO = require('js/dao/email-dao'),
OutboxBO = require('js/bo/outbox'),
appController = require('js/app-controller');
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');
describe('Navigation Controller unit test', function() {
var scope, ctrl, origEmailDao, emailDaoMock, outboxBoMock, outboxFolder, onConnectStub;
describe('Navigation Controller unit test', function() {
var scope, ctrl, origEmailDao, emailDaoMock, outboxBoMock, outboxFolder, onConnectStub;
beforeEach(function(done) {
// remember original module to restore later
origEmailDao = appController._emailDao;
emailDaoMock = sinon.createStubInstance(EmailDAO);
emailDaoMock._account = {
folders: [{
type: 'Inbox',
count: 2,
path: 'INBOX'
}, {
type: 'Outbox',
count: 0,
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();
beforeEach(function(done) {
// remember original module to restore later
origEmailDao = appController._emailDao;
emailDaoMock = sinon.createStubInstance(EmailDAO);
emailDaoMock._account = {
folders: [{
type: 'Inbox',
count: 2,
path: 'INBOX'
}, {
type: 'Outbox',
count: 0,
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) {
scope = $rootScope.$new();
scope.state = {};
ctrl = $controller(NavigationCtrl, {
$scope: scope,
$routeParams: {}
});
done();
angular.module('navigationtest', []);
mocks.module('navigationtest');
mocks.inject(function($rootScope, $controller) {
scope = $rootScope.$new();
scope.state = {};
ctrl = $controller(NavigationCtrl, {
$scope: scope,
$routeParams: {}
});
done();
});
});
afterEach(function() {
// restore the module
appController._emailDao = origEmailDao;
onConnectStub.restore();
afterEach(function() {
// restore the module
appController._emailDao = origEmailDao;
onConnectStub.restore();
});
describe('initial state', function() {
it('should be well defined', function() {
expect(scope.state).to.exist;
expect(scope.state.lightbox).to.be.undefined;
expect(scope.account.folders).to.not.be.empty;
expect(scope.openFolder).to.exist;
});
});
describe('initial state', function() {
it('should be well defined', function() {
expect(scope.state).to.exist;
expect(scope.state.lightbox).to.be.undefined;
expect(scope.account.folders).to.not.be.empty;
expect(scope.openFolder).to.exist;
});
describe('open/close nav view', function() {
it('should open/close', function() {
expect(scope.state.nav.open).to.be.false;
scope.state.nav.toggle(true);
expect(scope.state.nav.open).to.be.true;
scope.state.nav.toggle(false);
expect(scope.state.nav.open).to.be.false;
});
});
describe('open/close nav view', function() {
it('should open/close', function() {
expect(scope.state.nav.open).to.be.false;
scope.state.nav.toggle(true);
expect(scope.state.nav.open).to.be.true;
scope.state.nav.toggle(false);
expect(scope.state.nav.open).to.be.false;
});
describe('open folder', function() {
it('should work', function() {
scope.state.nav.open = true;
scope.openFolder('asd');
expect(scope.state.nav.currentFolder).to.equal('asd');
expect(scope.state.nav.open).to.be.false;
});
});
describe('open folder', function() {
it('should work', function() {
scope.state.nav.open = true;
describe('empty outbox', function() {
it('should work', function() {
var callback;
scope.openFolder('asd');
expect(scope.state.nav.currentFolder).to.equal('asd');
expect(scope.state.nav.open).to.be.false;
});
});
expect(outboxBoMock.startChecking.callCount).to.equal(1);
describe('empty outbox', function() {
it('should work', function() {
var callback;
outboxBoMock.startChecking.calledWith(sinon.match(function(cb) {
callback = cb;
}));
expect(outboxBoMock.startChecking.callCount).to.equal(1);
outboxBoMock.startChecking.calledWith(sinon.match(function(cb) {
callback = cb;
}));
callback(null, 5);
expect(outboxFolder.count).to.equal(5);
});
callback(null, 5);
expect(outboxFolder.count).to.equal(5);
});
});
});

View File

@ -1,201 +1,198 @@
define(function(require) {
'use strict';
'use strict';
var OAuth = require('js/util/oauth'),
RestDAO = require('js/dao/rest-dao'),
expect = chai.expect;
var OAuth = require('../../src/js/util/oauth'),
RestDAO = require('../../src/js/dao/rest-dao');
describe('OAuth unit tests', function() {
var oauth, googleApiStub, identityStub, getPlatformInfoStub, removeCachedStub,
testEmail = 'test@example.com';
describe('OAuth unit tests', function() {
var oauth, googleApiStub, identityStub, getPlatformInfoStub, removeCachedStub,
testEmail = 'test@example.com';
beforeEach(function() {
googleApiStub = sinon.createStubInstance(RestDAO);
oauth = new OAuth(googleApiStub);
window.chrome = window.chrome || {};
window.chrome.identity = window.chrome.identity || {};
if (typeof window.chrome.identity.getAuthToken !== 'function') {
window.chrome.identity.getAuthToken = function() {};
}
identityStub = sinon.stub(window.chrome.identity, 'getAuthToken');
if (typeof window.chrome.identity.removeCachedAuthToken !== 'function') {
window.chrome.identity.removeCachedAuthToken = function() {};
}
removeCachedStub = sinon.stub(window.chrome.identity, 'removeCachedAuthToken');
window.chrome.runtime = window.chrome.runtime || {};
if (typeof window.chrome.runtime.getPlatformInfo !== 'function') {
window.chrome.runtime.getPlatformInfo = function() {};
}
getPlatformInfoStub = sinon.stub(window.chrome.runtime, 'getPlatformInfo');
});
afterEach(function() {
identityStub.restore();
getPlatformInfoStub.restore();
removeCachedStub.restore();
});
describe('isSupported', function() {
it('should work', function() {
expect(oauth.isSupported()).to.be.true;
});
});
describe('refreshToken', function() {
var getOAuthTokenStub;
beforeEach(function() {
googleApiStub = sinon.createStubInstance(RestDAO);
oauth = new OAuth(googleApiStub);
window.chrome = window.chrome || {};
window.chrome.identity = window.chrome.identity || {};
if (typeof window.chrome.identity.getAuthToken !== 'function') {
window.chrome.identity.getAuthToken = function() {};
}
identityStub = sinon.stub(window.chrome.identity, 'getAuthToken');
if (typeof window.chrome.identity.removeCachedAuthToken !== 'function') {
window.chrome.identity.removeCachedAuthToken = function() {};
}
removeCachedStub = sinon.stub(window.chrome.identity, 'removeCachedAuthToken');
window.chrome.runtime = window.chrome.runtime || {};
if (typeof window.chrome.runtime.getPlatformInfo !== 'function') {
window.chrome.runtime.getPlatformInfo = function() {};
}
getPlatformInfoStub = sinon.stub(window.chrome.runtime, 'getPlatformInfo');
getOAuthTokenStub = sinon.stub(oauth, 'getOAuthToken');
});
afterEach(function() {
identityStub.restore();
getPlatformInfoStub.restore();
removeCachedStub.restore();
getOAuthTokenStub.restore();
});
describe('isSupported', function() {
it('should work', function() {
expect(oauth.isSupported()).to.be.true;
it('should work', function() {
removeCachedStub.withArgs({
token: 'oldToken'
}).yields();
getOAuthTokenStub.withArgs(testEmail).yields();
oauth.refreshToken({
oldToken: 'oldToken',
emailAddress: testEmail
}, function(err) {
expect(err).to.not.exist;
expect(removeCachedStub.calledOnce).to.be.true;
expect(getOAuthTokenStub.calledOnce).to.be.true;
});
});
describe('refreshToken', function() {
var getOAuthTokenStub;
it('should work without email', function() {
removeCachedStub.withArgs({
token: 'oldToken'
}).yields();
beforeEach(function() {
getOAuthTokenStub = sinon.stub(oauth, 'getOAuthToken');
});
afterEach(function() {
getOAuthTokenStub.restore();
});
getOAuthTokenStub.withArgs(undefined).yields();
it('should work', function() {
removeCachedStub.withArgs({
token: 'oldToken'
}).yields();
getOAuthTokenStub.withArgs(testEmail).yields();
oauth.refreshToken({
oldToken: 'oldToken',
emailAddress: testEmail
}, function(err) {
expect(err).to.not.exist;
expect(removeCachedStub.calledOnce).to.be.true;
expect(getOAuthTokenStub.calledOnce).to.be.true;
});
});
it('should work without email', function() {
removeCachedStub.withArgs({
token: 'oldToken'
}).yields();
getOAuthTokenStub.withArgs(undefined).yields();
oauth.refreshToken({
oldToken: 'oldToken',
}, function(err) {
expect(err).to.not.exist;
expect(removeCachedStub.calledOnce).to.be.true;
expect(getOAuthTokenStub.calledOnce).to.be.true;
expect(getOAuthTokenStub.calledWith(undefined)).to.be.true;
});
});
it('should fail without all options', function() {
oauth.refreshToken({
emailAddress: testEmail
}, function(err) {
expect(err).to.exist;
expect(removeCachedStub.called).to.be.false;
expect(getOAuthTokenStub.called).to.be.false;
});
oauth.refreshToken({
oldToken: 'oldToken',
}, function(err) {
expect(err).to.not.exist;
expect(removeCachedStub.calledOnce).to.be.true;
expect(getOAuthTokenStub.calledOnce).to.be.true;
expect(getOAuthTokenStub.calledWith(undefined)).to.be.true;
});
});
describe('getOAuthToken', function() {
it('should work for empty emailAddress', function(done) {
getPlatformInfoStub.yields({
os: 'android'
});
identityStub.withArgs({
interactive: true
}).yields('token');
oauth.getOAuthToken(undefined, function(err, token) {
expect(err).to.not.exist;
expect(token).to.equal('token');
done();
});
});
it('should work on android app', function(done) {
getPlatformInfoStub.yields({
os: 'android'
});
identityStub.withArgs({
interactive: true,
accountHint: testEmail
}).yields('token');
oauth.getOAuthToken(testEmail, function(err, token) {
expect(err).to.not.exist;
expect(token).to.equal('token');
done();
});
});
it('should work on desktop chrome', function(done) {
getPlatformInfoStub.yields({
os: 'mac'
});
identityStub.withArgs({
interactive: true
}).yields('token');
oauth.getOAuthToken(testEmail, function(err, token) {
expect(err).to.not.exist;
expect(token).to.equal('token');
done();
});
});
it('should fail', function(done) {
getPlatformInfoStub.yields({
os: 'android'
});
identityStub.yields();
oauth.getOAuthToken(testEmail, function(err, token) {
expect(err).to.exist;
expect(token).to.not.exist;
done();
});
it('should fail without all options', function() {
oauth.refreshToken({
emailAddress: testEmail
}, function(err) {
expect(err).to.exist;
expect(removeCachedStub.called).to.be.false;
expect(getOAuthTokenStub.called).to.be.false;
});
});
describe('queryEmailAddress', function() {
it('should work', function(done) {
googleApiStub.get.withArgs({
uri: '/oauth2/v3/userinfo?access_token=token'
}).yields(null, {
email: 'asdf@example.com'
});
oauth.queryEmailAddress('token', function(err, emailAddress) {
expect(err).to.not.exist;
expect(emailAddress).to.equal('asdf@example.com');
done();
});
});
it('should fail due to invalid token', function(done) {
oauth.queryEmailAddress('', function(err, emailAddress) {
expect(err).to.exist;
expect(emailAddress).to.not.exist;
done();
});
});
it('should fail due to error in rest api', function(done) {
googleApiStub.get.withArgs({
uri: '/oauth2/v3/userinfo?access_token=token'
}).yields(new Error());
oauth.queryEmailAddress('token', function(err, emailAddress) {
expect(err).to.exist;
expect(emailAddress).to.not.exist;
done();
});
});
});
});
describe('getOAuthToken', function() {
it('should work for empty emailAddress', function(done) {
getPlatformInfoStub.yields({
os: 'android'
});
identityStub.withArgs({
interactive: true
}).yields('token');
oauth.getOAuthToken(undefined, function(err, token) {
expect(err).to.not.exist;
expect(token).to.equal('token');
done();
});
});
it('should work on android app', function(done) {
getPlatformInfoStub.yields({
os: 'android'
});
identityStub.withArgs({
interactive: true,
accountHint: testEmail
}).yields('token');
oauth.getOAuthToken(testEmail, function(err, token) {
expect(err).to.not.exist;
expect(token).to.equal('token');
done();
});
});
it('should work on desktop chrome', function(done) {
getPlatformInfoStub.yields({
os: 'mac'
});
identityStub.withArgs({
interactive: true
}).yields('token');
oauth.getOAuthToken(testEmail, function(err, token) {
expect(err).to.not.exist;
expect(token).to.equal('token');
done();
});
});
it('should fail', function(done) {
getPlatformInfoStub.yields({
os: 'android'
});
identityStub.yields();
oauth.getOAuthToken(testEmail, function(err, token) {
expect(err).to.exist;
expect(token).to.not.exist;
done();
});
});
});
describe('queryEmailAddress', function() {
it('should work', function(done) {
googleApiStub.get.withArgs({
uri: '/oauth2/v3/userinfo?access_token=token'
}).yields(null, {
email: 'asdf@example.com'
});
oauth.queryEmailAddress('token', function(err, emailAddress) {
expect(err).to.not.exist;
expect(emailAddress).to.equal('asdf@example.com');
done();
});
});
it('should fail due to invalid token', function(done) {
oauth.queryEmailAddress('', function(err, emailAddress) {
expect(err).to.exist;
expect(emailAddress).to.not.exist;
done();
});
});
it('should fail due to error in rest api', function(done) {
googleApiStub.get.withArgs({
uri: '/oauth2/v3/userinfo?access_token=token'
}).yields(new Error());
oauth.queryEmailAddress('token', function(err, emailAddress) {
expect(err).to.exist;
expect(emailAddress).to.not.exist;
done();
});
});
});
});

View File

@ -1,279 +1,276 @@
define(function(require) {
'use strict';
'use strict';
var expect = chai.expect,
OutboxBO = require('js/bo/outbox'),
KeychainDAO = require('js/dao/keychain-dao'),
EmailDAO = require('js/dao/email-dao'),
DeviceStorageDAO = require('js/dao/devicestorage-dao');
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');
chai.Assertion.includeStack = true;
chai.Assertion.includeStack = true;
describe('Outbox Business Object unit test', function() {
var outbox, emailDaoStub, devicestorageStub, keychainStub,
dummyUser = 'spiderpig@springfield.com';
describe('Outbox Business Object unit test', function() {
var outbox, emailDaoStub, devicestorageStub, keychainStub,
dummyUser = 'spiderpig@springfield.com';
beforeEach(function() {
emailDaoStub = sinon.createStubInstance(EmailDAO);
emailDaoStub._account = {
emailAddress: dummyUser,
folders: [{
type: 'Outbox'
}],
online: true
beforeEach(function() {
emailDaoStub = sinon.createStubInstance(EmailDAO);
emailDaoStub._account = {
emailAddress: dummyUser,
folders: [{
type: 'Outbox'
}],
online: true
};
devicestorageStub = sinon.createStubInstance(DeviceStorageDAO);
keychainStub = sinon.createStubInstance(KeychainDAO);
outbox = new OutboxBO(emailDaoStub, keychainStub, devicestorageStub);
});
afterEach(function() {});
describe('start/stop checking', function() {
it('should work', function() {
function onOutboxUpdate(err) {
expect(err).to.not.exist;
}
outbox.startChecking(onOutboxUpdate);
expect(outbox._intervalId).to.exist;
outbox.stopChecking();
expect(outbox._intervalId).to.not.exist;
});
});
describe('put', function() {
it('should not encrypt and store a mail', function(done) {
var mail, senderKey, receiverKey;
senderKey = {
publicKey: 'SENDER PUBLIC KEY'
};
devicestorageStub = sinon.createStubInstance(DeviceStorageDAO);
keychainStub = sinon.createStubInstance(KeychainDAO);
outbox = new OutboxBO(emailDaoStub, keychainStub, devicestorageStub);
});
afterEach(function() {});
describe('start/stop checking', function() {
it('should work', function() {
function onOutboxUpdate(err) {
expect(err).to.not.exist;
}
outbox.startChecking(onOutboxUpdate);
expect(outbox._intervalId).to.exist;
outbox.stopChecking();
expect(outbox._intervalId).to.not.exist;
});
});
describe('put', function() {
it('should not encrypt and store a mail', function(done) {
var mail, senderKey, receiverKey;
senderKey = {
publicKey: 'SENDER PUBLIC KEY'
};
receiverKey = {
publicKey: 'RECEIVER PUBLIC KEY'
};
mail = {
from: [{
name: 'member',
address: 'member@whiteout.io'
}],
to: [{
name: 'member',
address: 'member'
}, {
name: 'notamember',
address: 'notamember'
}],
cc: [],
bcc: []
};
keychainStub.getReceiverPublicKey.withArgs(mail.from[0].address).yieldsAsync(null, senderKey);
keychainStub.getReceiverPublicKey.withArgs(mail.to[0].address).yieldsAsync(null, receiverKey);
keychainStub.getReceiverPublicKey.withArgs(mail.to[1].address).yieldsAsync();
devicestorageStub.storeList.withArgs([mail]).yieldsAsync();
outbox.put(mail, function(error) {
expect(error).to.not.exist;
expect(mail.publicKeysArmored.length).to.equal(2);
expect(emailDaoStub.encrypt.called).to.be.false;
expect(devicestorageStub.storeList.calledOnce).to.be.true;
done();
});
});
it('should not encrypt a mail with bcc and store a mail', function(done) {
var mail;
mail = {
from: [{
name: 'member',
address: 'member@whiteout.io'
}],
to: [{
name: 'member',
address: 'member@whiteout.io'
}],
cc: [],
bcc: [{
name: 'member',
address: 'member@whiteout.io'
}]
};
devicestorageStub.storeList.withArgs([mail]).yieldsAsync();
outbox.put(mail, function(error) {
expect(error).to.not.exist;
expect(mail.publicKeysArmored.length).to.equal(0);
expect(keychainStub.getReceiverPublicKey.called).to.be.false;
expect(emailDaoStub.encrypt.called).to.be.false;
expect(devicestorageStub.storeList.calledOnce).to.be.true;
done();
});
});
it('should encrypt and store a mail', function(done) {
var mail, senderKey, receiverKey;
senderKey = {
publicKey: 'SENDER PUBLIC KEY'
};
receiverKey = {
publicKey: 'RECEIVER PUBLIC KEY'
};
mail = {
from: [{
name: 'member',
address: 'member@whiteout.io'
}],
to: [{
name: 'member',
address: 'member'
}, {
name: 'notamember',
address: 'notamember'
}],
cc: [],
bcc: []
};
keychainStub.getReceiverPublicKey.withArgs(mail.from[0].address).yieldsAsync(null, senderKey);
keychainStub.getReceiverPublicKey.withArgs(mail.to[0].address).yieldsAsync(null, receiverKey);
keychainStub.getReceiverPublicKey.withArgs(mail.to[1].address).yieldsAsync(null, receiverKey);
emailDaoStub.encrypt.withArgs({
mail: mail,
publicKeysArmored: [senderKey.publicKey, receiverKey.publicKey, receiverKey.publicKey]
}).yieldsAsync();
devicestorageStub.storeList.withArgs([mail]).yieldsAsync();
outbox.put(mail, function(error) {
expect(error).to.not.exist;
expect(mail.publicKeysArmored.length).to.equal(3);
expect(emailDaoStub.encrypt.calledOnce).to.be.true;
expect(devicestorageStub.storeList.calledOnce).to.be.true;
done();
});
});
});
describe('process outbox', function() {
it('should send to registered users and update pending mails', function(done) {
var from, member, invited, notinvited, newlyjoined, dummyMails, newlyjoinedKey;
from = [{
receiverKey = {
publicKey: 'RECEIVER PUBLIC KEY'
};
mail = {
from: [{
name: 'member',
address: 'member@whiteout.io'
}];
member = {
id: '12',
from: from,
to: [{
name: 'member',
address: 'member'
}],
encrypted: true,
publicKeysArmored: ['ARMORED KEY OF MEMBER'],
unregisteredUsers: []
};
invited = {
id: '34',
from: from,
to: [{
name: 'invited',
address: 'invited'
}],
publicKeysArmored: [],
unregisteredUsers: [{
name: 'invited',
address: 'invited'
}]
};
notinvited = {
id: '56',
from: from,
to: [{
name: 'notinvited',
address: 'notinvited'
}],
publicKeysArmored: [],
unregisteredUsers: [{
name: 'notinvited',
address: 'notinvited'
}]
};
newlyjoined = {
id: '78',
from: from,
to: [{
name: 'newlyjoined',
address: 'newlyjoined'
}],
encrypted: true,
publicKeysArmored: [],
unregisteredUsers: [{
name: 'newlyjoined',
address: 'newlyjoined'
}]
};
newlyjoinedKey = {
publicKey: 'THIS IS THE NEWLY JOINED PUBLIC KEY!'
};
}],
to: [{
name: 'member',
address: 'member'
}, {
name: 'notamember',
address: 'notamember'
}],
cc: [],
bcc: []
};
dummyMails = [member, invited, notinvited, newlyjoined];
keychainStub.getReceiverPublicKey.withArgs(mail.from[0].address).yieldsAsync(null, senderKey);
keychainStub.getReceiverPublicKey.withArgs(mail.to[0].address).yieldsAsync(null, receiverKey);
keychainStub.getReceiverPublicKey.withArgs(mail.to[1].address).yieldsAsync();
devicestorageStub.listItems.yieldsAsync(null, dummyMails);
devicestorageStub.storeList.withArgs([mail]).yieldsAsync();
emailDaoStub.sendPlaintext.yieldsAsync();
outbox.put(mail, function(error) {
expect(error).to.not.exist;
emailDaoStub.sendEncrypted.withArgs({
email: newlyjoined
}).yieldsAsync();
expect(mail.publicKeysArmored.length).to.equal(2);
expect(emailDaoStub.encrypt.called).to.be.false;
expect(devicestorageStub.storeList.calledOnce).to.be.true;
emailDaoStub.sendEncrypted.withArgs({
email: member
}).yieldsAsync();
devicestorageStub.removeList.yieldsAsync();
function onOutboxUpdate(err, count) {
expect(err).to.not.exist;
expect(count).to.equal(0);
expect(outbox._outboxBusy).to.be.false;
expect(emailDaoStub.sendEncrypted.callCount).to.equal(2);
expect(emailDaoStub.sendPlaintext.callCount).to.equal(2);
expect(devicestorageStub.listItems.callCount).to.equal(1);
expect(devicestorageStub.removeList.callCount).to.equal(4);
expect(keychainStub.getReceiverPublicKey.callCount).to.equal(0);
done();
}
outbox._processOutbox(onOutboxUpdate);
done();
});
});
it('should not process outbox in offline mode', function(done) {
emailDaoStub._account.online = false;
devicestorageStub.listItems.yieldsAsync(null, [{}]);
it('should not encrypt a mail with bcc and store a mail', function(done) {
var mail;
outbox._processOutbox(function(err, count) {
expect(err).to.not.exist;
expect(count).to.equal(1);
expect(devicestorageStub.listItems.callCount).to.equal(1);
expect(outbox._outboxBusy).to.be.false;
done();
});
mail = {
from: [{
name: 'member',
address: 'member@whiteout.io'
}],
to: [{
name: 'member',
address: 'member@whiteout.io'
}],
cc: [],
bcc: [{
name: 'member',
address: 'member@whiteout.io'
}]
};
devicestorageStub.storeList.withArgs([mail]).yieldsAsync();
outbox.put(mail, function(error) {
expect(error).to.not.exist;
expect(mail.publicKeysArmored.length).to.equal(0);
expect(keychainStub.getReceiverPublicKey.called).to.be.false;
expect(emailDaoStub.encrypt.called).to.be.false;
expect(devicestorageStub.storeList.calledOnce).to.be.true;
done();
});
});
it('should encrypt and store a mail', function(done) {
var mail, senderKey, receiverKey;
senderKey = {
publicKey: 'SENDER PUBLIC KEY'
};
receiverKey = {
publicKey: 'RECEIVER PUBLIC KEY'
};
mail = {
from: [{
name: 'member',
address: 'member@whiteout.io'
}],
to: [{
name: 'member',
address: 'member'
}, {
name: 'notamember',
address: 'notamember'
}],
cc: [],
bcc: []
};
keychainStub.getReceiverPublicKey.withArgs(mail.from[0].address).yieldsAsync(null, senderKey);
keychainStub.getReceiverPublicKey.withArgs(mail.to[0].address).yieldsAsync(null, receiverKey);
keychainStub.getReceiverPublicKey.withArgs(mail.to[1].address).yieldsAsync(null, receiverKey);
emailDaoStub.encrypt.withArgs({
mail: mail,
publicKeysArmored: [senderKey.publicKey, receiverKey.publicKey, receiverKey.publicKey]
}).yieldsAsync();
devicestorageStub.storeList.withArgs([mail]).yieldsAsync();
outbox.put(mail, function(error) {
expect(error).to.not.exist;
expect(mail.publicKeysArmored.length).to.equal(3);
expect(emailDaoStub.encrypt.calledOnce).to.be.true;
expect(devicestorageStub.storeList.calledOnce).to.be.true;
done();
});
});
});
describe('process outbox', function() {
it('should send to registered users and update pending mails', function(done) {
var from, member, invited, notinvited, newlyjoined, dummyMails, newlyjoinedKey;
from = [{
name: 'member',
address: 'member@whiteout.io'
}];
member = {
id: '12',
from: from,
to: [{
name: 'member',
address: 'member'
}],
encrypted: true,
publicKeysArmored: ['ARMORED KEY OF MEMBER'],
unregisteredUsers: []
};
invited = {
id: '34',
from: from,
to: [{
name: 'invited',
address: 'invited'
}],
publicKeysArmored: [],
unregisteredUsers: [{
name: 'invited',
address: 'invited'
}]
};
notinvited = {
id: '56',
from: from,
to: [{
name: 'notinvited',
address: 'notinvited'
}],
publicKeysArmored: [],
unregisteredUsers: [{
name: 'notinvited',
address: 'notinvited'
}]
};
newlyjoined = {
id: '78',
from: from,
to: [{
name: 'newlyjoined',
address: 'newlyjoined'
}],
encrypted: true,
publicKeysArmored: [],
unregisteredUsers: [{
name: 'newlyjoined',
address: 'newlyjoined'
}]
};
newlyjoinedKey = {
publicKey: 'THIS IS THE NEWLY JOINED PUBLIC KEY!'
};
dummyMails = [member, invited, notinvited, newlyjoined];
devicestorageStub.listItems.yieldsAsync(null, dummyMails);
emailDaoStub.sendPlaintext.yieldsAsync();
emailDaoStub.sendEncrypted.withArgs({
email: newlyjoined
}).yieldsAsync();
emailDaoStub.sendEncrypted.withArgs({
email: member
}).yieldsAsync();
devicestorageStub.removeList.yieldsAsync();
function onOutboxUpdate(err, count) {
expect(err).to.not.exist;
expect(count).to.equal(0);
expect(outbox._outboxBusy).to.be.false;
expect(emailDaoStub.sendEncrypted.callCount).to.equal(2);
expect(emailDaoStub.sendPlaintext.callCount).to.equal(2);
expect(devicestorageStub.listItems.callCount).to.equal(1);
expect(devicestorageStub.removeList.callCount).to.equal(4);
expect(keychainStub.getReceiverPublicKey.callCount).to.equal(0);
done();
}
outbox._processOutbox(onOutboxUpdate);
});
it('should not process outbox in offline mode', function(done) {
emailDaoStub._account.online = false;
devicestorageStub.listItems.yieldsAsync(null, [{}]);
outbox._processOutbox(function(err, count) {
expect(err).to.not.exist;
expect(count).to.equal(1);
expect(devicestorageStub.listItems.callCount).to.equal(1);
expect(outbox._outboxBusy).to.be.false;
done();
});
});
});

View File

@ -1,464 +1,460 @@
define(function(require) {
'use strict';
'use strict';
var PGP = require('js/crypto/pgp'),
openpgp = require('openpgp'),
expect = chai.expect;
var PGP = require('../../src/js/crypto/pgp');
describe('PGP Crypto Api unit tests', function() {
this.timeout(20000);
describe('PGP Crypto Api unit tests', function() {
this.timeout(20000);
var pgp,
user = 'whiteout.test@t-online.de',
passphrase = 'asdf',
keySize = 512,
keyId = 'F6F60E9B42CDFF4C',
pubkey = '-----BEGIN PGP PUBLIC KEY BLOCK-----\r\n' +
'Version: OpenPGP.js v0.7.2\r\n' +
'Comment: Whiteout Mail - https://whiteout.io\r\n' +
'\r\n' +
'xk0EUlhMvAEB/2MZtCUOAYvyLFjDp3OBMGn3Ev8FwjzyPbIF0JUw+L7y2XR5\r\n' +
'RVGvbK88unV3cU/1tOYdNsXI6pSp/Ztjyv7vbBUAEQEAAc0pV2hpdGVvdXQg\r\n' +
'VXNlciA8d2hpdGVvdXQudGVzdEB0LW9ubGluZS5kZT7CXAQQAQgAEAUCUlhM\r\n' +
'vQkQ9vYOm0LN/0wAAAW4Af9C+kYW1AvNWmivdtr0M0iYCUjM9DNOQH1fcvXq\r\n' +
'IiN602mWrkd8jcEzLsW5IUNzVPLhrFIuKyBDTpLnC07Loce1\r\n' +
'=6XMW\r\n' +
'-----END PGP PUBLIC KEY BLOCK-----\r\n\r\n',
privkey = '-----BEGIN PGP PRIVATE KEY BLOCK-----\r\n' +
'Version: OpenPGP.js v0.7.2\r\n' +
'Comment: Whiteout Mail - https://whiteout.io\r\n' +
'\r\n' +
'xcBeBFJYTLwBAf9jGbQlDgGL8ixYw6dzgTBp9xL/BcI88j2yBdCVMPi+8tl0\r\n' +
'eUVRr2yvPLp1d3FP9bTmHTbFyOqUqf2bY8r+72wVABEBAAH+AwMIhNB4ivtv\r\n' +
'Y2xg6VeMcjjHxZayESHACV+nQx5Tx6ev6xzIF1Qh72fNPDppLhFSFOuTTMsU\r\n' +
'kTN4c+BVYt29spH+cA1jcDAxQ2ULrNAXo+hheOqhpedTs8aCbcLFkJAS16hk\r\n' +
'YSk4OnJgp/z24rVju1SHRSFbgundPzmNgXeX9e8IkviGhhQ11Wc5YwVkx03t\r\n' +
'Z3MdDMF0jyhopbPIoBdyJB0dhvBh98w3JmwpYh9wjUA9MBHD1tvHpRmSZ3BM\r\n' +
'UCmATn2ZLWBRWiYqFbgDnL1GM80pV2hpdGVvdXQgVXNlciA8d2hpdGVvdXQu\r\n' +
'dGVzdEB0LW9ubGluZS5kZT7CXAQQAQgAEAUCUlhMvQkQ9vYOm0LN/0wAAAW4\r\n' +
'Af9C+kYW1AvNWmivdtr0M0iYCUjM9DNOQH1fcvXqIiN602mWrkd8jcEzLsW5\r\n' +
'IUNzVPLhrFIuKyBDTpLnC07Loce1\r\n' +
'=ULta\r\n' +
'-----END PGP PRIVATE KEY BLOCK-----\r\n';
var pgp,
user = 'whiteout.test@t-online.de',
passphrase = 'asdf',
keySize = 512,
keyId = 'F6F60E9B42CDFF4C',
pubkey = '-----BEGIN PGP PUBLIC KEY BLOCK-----\r\n' +
'Version: OpenPGP.js v0.7.2\r\n' +
'Comment: Whiteout Mail - https://whiteout.io\r\n' +
'\r\n' +
'xk0EUlhMvAEB/2MZtCUOAYvyLFjDp3OBMGn3Ev8FwjzyPbIF0JUw+L7y2XR5\r\n' +
'RVGvbK88unV3cU/1tOYdNsXI6pSp/Ztjyv7vbBUAEQEAAc0pV2hpdGVvdXQg\r\n' +
'VXNlciA8d2hpdGVvdXQudGVzdEB0LW9ubGluZS5kZT7CXAQQAQgAEAUCUlhM\r\n' +
'vQkQ9vYOm0LN/0wAAAW4Af9C+kYW1AvNWmivdtr0M0iYCUjM9DNOQH1fcvXq\r\n' +
'IiN602mWrkd8jcEzLsW5IUNzVPLhrFIuKyBDTpLnC07Loce1\r\n' +
'=6XMW\r\n' +
'-----END PGP PUBLIC KEY BLOCK-----\r\n\r\n',
privkey = '-----BEGIN PGP PRIVATE KEY BLOCK-----\r\n' +
'Version: OpenPGP.js v0.7.2\r\n' +
'Comment: Whiteout Mail - https://whiteout.io\r\n' +
'\r\n' +
'xcBeBFJYTLwBAf9jGbQlDgGL8ixYw6dzgTBp9xL/BcI88j2yBdCVMPi+8tl0\r\n' +
'eUVRr2yvPLp1d3FP9bTmHTbFyOqUqf2bY8r+72wVABEBAAH+AwMIhNB4ivtv\r\n' +
'Y2xg6VeMcjjHxZayESHACV+nQx5Tx6ev6xzIF1Qh72fNPDppLhFSFOuTTMsU\r\n' +
'kTN4c+BVYt29spH+cA1jcDAxQ2ULrNAXo+hheOqhpedTs8aCbcLFkJAS16hk\r\n' +
'YSk4OnJgp/z24rVju1SHRSFbgundPzmNgXeX9e8IkviGhhQ11Wc5YwVkx03t\r\n' +
'Z3MdDMF0jyhopbPIoBdyJB0dhvBh98w3JmwpYh9wjUA9MBHD1tvHpRmSZ3BM\r\n' +
'UCmATn2ZLWBRWiYqFbgDnL1GM80pV2hpdGVvdXQgVXNlciA8d2hpdGVvdXQu\r\n' +
'dGVzdEB0LW9ubGluZS5kZT7CXAQQAQgAEAUCUlhMvQkQ9vYOm0LN/0wAAAW4\r\n' +
'Af9C+kYW1AvNWmivdtr0M0iYCUjM9DNOQH1fcvXqIiN602mWrkd8jcEzLsW5\r\n' +
'IUNzVPLhrFIuKyBDTpLnC07Loce1\r\n' +
'=ULta\r\n' +
'-----END PGP PRIVATE KEY BLOCK-----\r\n';
beforeEach(function() {
pgp = new PGP();
beforeEach(function() {
pgp = new PGP();
});
afterEach(function() {});
describe('Generate key pair', function() {
it('should fail', function(done) {
pgp.generateKeys({
emailAddress: 'whiteout.test@t-onlinede',
keySize: keySize,
passphrase: passphrase
}, function(err, keys) {
expect(err).to.exist;
expect(keys).to.not.exist;
done();
});
});
afterEach(function() {});
describe('Generate key pair', function() {
it('should fail', function(done) {
pgp.generateKeys({
emailAddress: 'whiteout.test@t-onlinede',
keySize: keySize,
passphrase: passphrase
}, function(err, keys) {
expect(err).to.exist;
expect(keys).to.not.exist;
done();
});
it('should fail', function(done) {
pgp.generateKeys({
emailAddress: 'whiteout.testt-online.de',
keySize: keySize,
passphrase: passphrase
}, function(err, keys) {
expect(err).to.exist;
expect(keys).to.not.exist;
done();
});
it('should fail', function(done) {
pgp.generateKeys({
emailAddress: 'whiteout.testt-online.de',
keySize: keySize,
passphrase: passphrase
}, function(err, keys) {
expect(err).to.exist;
expect(keys).to.not.exist;
done();
});
});
it('should work with passphrase', function(done) {
pgp.generateKeys({
emailAddress: user,
keySize: keySize,
passphrase: passphrase
}, function(err, keys) {
});
it('should work with passphrase', function(done) {
pgp.generateKeys({
emailAddress: user,
keySize: keySize,
passphrase: passphrase
}, function(err, keys) {
expect(err).to.not.exist;
expect(keys.keyId).to.exist;
expect(keys.privateKeyArmored).to.exist;
expect(keys.publicKeyArmored).to.exist;
// test encrypt/decrypt
pgp.importKeys({
passphrase: passphrase,
privateKeyArmored: keys.privateKeyArmored,
publicKeyArmored: keys.publicKeyArmored
}, function(err) {
expect(err).to.not.exist;
expect(keys.keyId).to.exist;
expect(keys.privateKeyArmored).to.exist;
expect(keys.publicKeyArmored).to.exist;
// test encrypt/decrypt
pgp.importKeys({
passphrase: passphrase,
privateKeyArmored: keys.privateKeyArmored,
publicKeyArmored: keys.publicKeyArmored
}, function(err) {
pgp.encrypt('secret', [keys.publicKeyArmored], function(err, ct) {
expect(err).to.not.exist;
expect(ct).to.exist;
pgp.encrypt('secret', [keys.publicKeyArmored], function(err, ct) {
pgp.decrypt(ct, keys.publicKeyArmored, function(err, pt, signValid) {
expect(err).to.not.exist;
expect(ct).to.exist;
pgp.decrypt(ct, keys.publicKeyArmored, function(err, pt, signValid) {
expect(err).to.not.exist;
expect(pt).to.equal('secret');
expect(signValid).to.be.true;
done();
});
});
});
});
});
it('should work without passphrase', function(done) {
pgp.generateKeys({
emailAddress: user,
keySize: keySize,
passphrase: ''
}, function(err, keys) {
expect(err).to.not.exist;
expect(keys.keyId).to.exist;
expect(keys.privateKeyArmored).to.exist;
expect(keys.publicKeyArmored).to.exist;
// test encrypt/decrypt
pgp.importKeys({
passphrase: undefined,
privateKeyArmored: keys.privateKeyArmored,
publicKeyArmored: keys.publicKeyArmored
}, function(err) {
expect(err).to.not.exist;
pgp.encrypt('secret', [keys.publicKeyArmored], function(err, ct) {
expect(err).to.not.exist;
expect(ct).to.exist;
pgp.decrypt(ct, keys.publicKeyArmored, function(err, pt, signValid) {
expect(err).to.not.exist;
expect(pt).to.equal('secret');
expect(signValid).to.be.true;
done();
});
expect(pt).to.equal('secret');
expect(signValid).to.be.true;
done();
});
});
});
});
});
it('should work without passphrase', function(done) {
pgp.generateKeys({
emailAddress: user,
keySize: keySize,
passphrase: ''
}, function(err, keys) {
expect(err).to.not.exist;
expect(keys.keyId).to.exist;
expect(keys.privateKeyArmored).to.exist;
expect(keys.publicKeyArmored).to.exist;
describe('Import/Export key pair', function() {
it('should fail', function(done) {
// test encrypt/decrypt
pgp.importKeys({
passphrase: 'asd',
privateKeyArmored: privkey,
publicKeyArmored: pubkey
}, function(err) {
expect(err).to.exist;
expect(err.message).to.equal('Incorrect passphrase!');
pgp.exportKeys(function(err, keys) {
expect(err).to.exist;
expect(keys).to.not.exist;
done();
});
});
});
it('should work', function(done) {
pgp.importKeys({
passphrase: passphrase,
privateKeyArmored: privkey,
publicKeyArmored: pubkey
passphrase: undefined,
privateKeyArmored: keys.privateKeyArmored,
publicKeyArmored: keys.publicKeyArmored
}, function(err) {
expect(err).to.not.exist;
pgp.exportKeys(function(err, keys) {
expect(err).to.not.exist;
expect(keys.keyId).to.equal(keyId);
expect(keys.privateKeyArmored.replace(/\r/g, '')).to.equal(privkey.replace(/\r/g, ''));
expect(keys.publicKeyArmored.replace(/\r/g, '')).to.equal(pubkey.replace(/\r/g, ''));
done();
});
});
});
});
describe('Change passphrase of private key', function() {
it('should work with new passphrase', function(done) {
pgp.changePassphrase({
privateKeyArmored: privkey,
oldPassphrase: passphrase,
newPassphrase: 'yxcv'
}, function(err, reEncryptedKey) {
expect(err).to.not.exist;
expect(reEncryptedKey).to.exist;
pgp.importKeys({
passphrase: 'yxcv',
privateKeyArmored: reEncryptedKey,
publicKeyArmored: pubkey
}, function(err) {
expect(err).to.not.exist;
done();
});
});
});
it('should work with empty passphrase', function(done) {
pgp.changePassphrase({
privateKeyArmored: privkey,
oldPassphrase: passphrase,
newPassphrase: undefined
}, function(err, reEncryptedKey) {
expect(err).to.not.exist;
expect(reEncryptedKey).to.exist;
pgp.importKeys({
passphrase: undefined,
privateKeyArmored: reEncryptedKey,
publicKeyArmored: pubkey
}, function(err) {
expect(err).to.not.exist;
done();
});
});
});
it('should fail when passphrases are equal', function(done) {
pgp.changePassphrase({
privateKeyArmored: privkey,
oldPassphrase: passphrase,
newPassphrase: passphrase
}, function(err, reEncryptedKey) {
expect(err).to.exist;
expect(reEncryptedKey).to.not.exist;
done();
});
});
it('should fail when old passphrase is incorrect', function(done) {
pgp.changePassphrase({
privateKeyArmored: privkey,
oldPassphrase: 'asd',
newPassphrase: 'yxcv'
}, function(err, reEncryptedKey) {
expect(err).to.exist;
expect(reEncryptedKey).to.not.exist;
done();
});
});
});
describe('Encrypt/Sign/Decrypt/Verify', function() {
var message = 'asdfs\n\nThursday, Nov 21, 2013 7:38 PM asdf@example.com wrote:\n' +
'> asdf\n' +
'> \n' +
'> Thursday, Nov 21, 2013 7:32 PM asdf@example.com wrote:\n' +
'> > secret 3';
var wrongPubkey = '-----BEGIN PGP PUBLIC KEY BLOCK-----\r\nVersion: OpenPGP.js v.1.20131116\r\nComment: Whiteout Mail - http://whiteout.io\r\n\r\nxsBNBFKODs4BB/9iOF4THsjQMY+WEpT7ShgKxj4bHzRRaQkqczS4nZvP0U3g\r\nqeqCnbpagyeKXA+bhWFQW4GmXtgAoeD5PXs6AZYrw3tWNxLKu2Oe6Tp9K/XI\r\nxTMQ2wl4qZKDXHvuPsJ7cmgaWqpPyXtxA4zHHS3WrkI/6VzHAcI/y6x4szSB\r\nKgSuhI3hjh3s7TybUC1U6AfoQGx/S7e3WwlCOrK8GTClirN/2mCPRC5wuIft\r\nnkoMfA6jK8d2OPrJ63shy5cgwHOjQg/xuk46dNS7tkvGmbaa+X0PgqSKB+Hf\r\nYPPNS/ylg911DH9qa8BqYU2QpNh9jUKXSF+HbaOM+plWkCSAL7czV+R3ABEB\r\nAAHNLVdoaXRlb3V0IFVzZXIgPHNhZmV3aXRobWUudGVzdHVzZXJAZ21haWwu\r\nY29tPsLAXAQQAQgAEAUCUo4O2gkQ1/uT/N+/wjwAAN2cB/9gFRmAfvEQ2qz+\r\nWubmT2EsSSnjPMxzG4uyykFoa+TaZCWo2Xa2tQghmU103kEkQb1OEjRjpgwJ\r\nYX9Kghnl8DByM686L5AXnRyHP78qRJCLXSXl0AGicboUDp5sovaa4rswQceH\r\nvcdWgZ/mgHTRoiQeJddy9k+H6MPFiyFaVcFwegVsmpc+dCcC8yT+qh8ZIbyG\r\nRJU60PmKKN7LUusP+8DbSv39zCGJCBlVVKyA4MzdF5uM+sqTdXbKzOrT5DGd\r\nCZaox4s+w16Sq1rHzZKFWfQPfKLDB9pyA0ufCVRA3AF6BUi7G3ZqhZiHNhMP\r\nNvE45V/hS1PbZcfPVoUjE2qc1Ix1\r\n=7Wpe\r\n-----END PGP PUBLIC KEY BLOCK-----';
beforeEach(function(done) {
pgp.importKeys({
passphrase: passphrase,
privateKeyArmored: privkey,
publicKeyArmored: pubkey
}, function(err) {
expect(err).to.not.exist;
done();
});
});
describe('Get KeyId', function() {
it('should work without param', function() {
var keyId = pgp.getKeyId();
expect(keyId).to.equal('F6F60E9B42CDFF4C');
});
it('should work with param', function() {
var keyId = pgp.getKeyId(pubkey);
expect(keyId).to.equal('F6F60E9B42CDFF4C');
});
});
describe('Get Fingerprint', function() {
it('should work without param', function() {
var fingerprint = pgp.getFingerprint();
expect(fingerprint).to.equal('5856CEF789C3A307E8A1B976F6F60E9B42CDFF4C');
});
it('should work with param', function() {
var fingerprint = pgp.getFingerprint(pubkey);
expect(fingerprint).to.equal('5856CEF789C3A307E8A1B976F6F60E9B42CDFF4C');
});
});
describe('getKeyParams', function() {
it('should work with param', function() {
var params = pgp.getKeyParams(pubkey);
expect(params.fingerprint).to.equal('5856CEF789C3A307E8A1B976F6F60E9B42CDFF4C');
expect(params._id).to.equal("F6F60E9B42CDFF4C");
expect(params.bitSize).to.equal(keySize);
expect(params.userId).to.equal("whiteout.test@t-online.de");
expect(params.userIds[0].name).to.equal("Whiteout User");
expect(params.userIds[0].emailAddress).to.equal("whiteout.test@t-online.de");
expect(params.algorithm).to.equal("rsa_encrypt_sign");
});
it('should work without param', function() {
var params = pgp.getKeyParams();
expect(params.fingerprint).to.equal('5856CEF789C3A307E8A1B976F6F60E9B42CDFF4C');
expect(params._id).to.equal("F6F60E9B42CDFF4C");
expect(params.bitSize).to.equal(keySize);
expect(params.userId).to.equal("whiteout.test@t-online.de");
expect(params.userIds[0].name).to.equal("Whiteout User");
expect(params.userIds[0].emailAddress).to.equal("whiteout.test@t-online.de");
expect(params.algorithm).to.equal("rsa_encrypt_sign");
});
});
describe('extractPublicKey', function() {
it('should work', function() {
var pk = pgp.extractPublicKey(privkey);
expect(pk).to.exist;
expect(pk).to.contain('-----BEGIN PGP PUBLIC KEY BLOCK-----');
});
});
describe('Encrypt and sign', function() {
it('should fail', function(done) {
var input = null;
pgp.encrypt(input, [pubkey], function(err, ct) {
expect(err).to.exist;
expect(ct).to.not.exist;
done();
});
});
it('should work', function(done) {
pgp.encrypt(message, [pubkey], function(err, ct) {
pgp.encrypt('secret', [keys.publicKeyArmored], function(err, ct) {
expect(err).to.not.exist;
expect(ct).to.exist;
done();
});
});
it('should encrypt to myself if public keys are empty', function(done) {
pgp.encrypt(message, undefined, function(err, ct) {
expect(err).to.not.exist;
expect(ct).to.exist;
done();
});
});
});
describe('Decrypt and verify', function() {
var ciphertext;
beforeEach(function(done) {
pgp.encrypt(message, [pubkey], function(err, ct) {
expect(err).to.not.exist;
expect(ct).to.exist;
ciphertext = ct;
done();
});
});
it('should fail', function(done) {
var input = 'asdfa\rsdf';
pgp.decrypt(input, pubkey, function(err, pt) {
expect(err).to.exist;
expect(pt).to.not.exist;
done();
});
});
it('should work', function(done) {
pgp.decrypt(ciphertext, pubkey, function(err, pt, signValid) {
expect(err).to.not.exist;
expect(pt).to.equal(message);
expect(signValid).to.be.true;
done();
});
});
it('should work without signature', function(done) {
var ct = openpgp.encryptMessage([pgp._publicKey], message);
pgp.decrypt(ct, undefined, function(err, pt, signValid) {
expect(err).to.not.exist;
expect(pt).to.equal(message);
expect(signValid).to.be.undefined;
done();
});
});
it('should fail to verify if public keys are empty', function(done) {
// setup another public key so that signature verification fails
pgp._publicKey = openpgp.key.readArmored(wrongPubkey).keys[0];
pgp.decrypt(ciphertext, undefined, function(err, pt, signValid) {
expect(err).to.not.exist;
expect(pt).to.equal(message);
expect(signValid).to.be.null;
done();
});
});
it('should decrypt but signValid should be null for wrong public key', function(done) {
pgp.decrypt(ciphertext, wrongPubkey, function(err, pt, signValid) {
expect(err).to.not.exist;
expect(pt).to.equal(message);
expect(signValid).to.be.null;
done();
});
});
});
describe('Verify clearsigned message', function() {
var clearsigned;
beforeEach(function() {
clearsigned = openpgp.signClearMessage(pgp._privateKey, 'this is a clearsigned message');
});
it('should work', function(done) {
pgp.verifyClearSignedMessage(clearsigned, pubkey, function(err, signaturesValid) {
expect(err).to.not.exist;
expect(signaturesValid).to.be.true;
done();
});
});
it('should fail', function(done) {
pgp.verifyClearSignedMessage(clearsigned.replace('clearsigned', 'invalid'), pubkey, function(err, signaturesValid) {
expect(err).to.not.exist;
expect(signaturesValid).to.be.false;
done();
});
});
it('should be null for wrong public key', function(done) {
pgp.verifyClearSignedMessage(clearsigned, wrongPubkey, function(err, signaturesValid) {
expect(err).to.not.exist;
expect(signaturesValid).to.be.null;
done();
});
});
});
describe('Verify detached signature', function() {
var signedMessage, signature;
beforeEach(function() {
signedMessage = 'this is a signed message';
var clearsigned = openpgp.signClearMessage(pgp._privateKey, signedMessage);
var signatureHeader = '-----BEGIN PGP SIGNATURE-----';
signature = signatureHeader + clearsigned.split(signatureHeader).pop();
});
it('should work', function(done) {
pgp.verifySignedMessage(signedMessage, signature, pubkey, function(err, signaturesValid) {
expect(err).to.not.exist;
expect(signaturesValid).to.be.true;
done();
});
});
it('should fail', function(done) {
pgp.verifySignedMessage(signedMessage.replace('signed', 'invalid'), signature, pubkey, function(err, signaturesValid) {
expect(err).to.not.exist;
expect(signaturesValid).to.be.false;
done();
});
});
it('should be null for wrong public key', function(done) {
pgp.verifySignedMessage(signedMessage, signature, wrongPubkey, function(err, signaturesValid) {
expect(err).to.not.exist;
expect(signaturesValid).to.be.null;
done();
pgp.decrypt(ct, keys.publicKeyArmored, function(err, pt, signValid) {
expect(err).to.not.exist;
expect(pt).to.equal('secret');
expect(signValid).to.be.true;
done();
});
});
});
});
});
});
describe('Import/Export key pair', function() {
it('should fail', function(done) {
pgp.importKeys({
passphrase: 'asd',
privateKeyArmored: privkey,
publicKeyArmored: pubkey
}, function(err) {
expect(err).to.exist;
expect(err.message).to.equal('Incorrect passphrase!');
pgp.exportKeys(function(err, keys) {
expect(err).to.exist;
expect(keys).to.not.exist;
done();
});
});
});
it('should work', function(done) {
pgp.importKeys({
passphrase: passphrase,
privateKeyArmored: privkey,
publicKeyArmored: pubkey
}, function(err) {
expect(err).to.not.exist;
pgp.exportKeys(function(err, keys) {
expect(err).to.not.exist;
expect(keys.keyId).to.equal(keyId);
expect(keys.privateKeyArmored.replace(/\r/g, '')).to.equal(privkey.replace(/\r/g, ''));
expect(keys.publicKeyArmored.replace(/\r/g, '')).to.equal(pubkey.replace(/\r/g, ''));
done();
});
});
});
});
describe('Change passphrase of private key', function() {
it('should work with new passphrase', function(done) {
pgp.changePassphrase({
privateKeyArmored: privkey,
oldPassphrase: passphrase,
newPassphrase: 'yxcv'
}, function(err, reEncryptedKey) {
expect(err).to.not.exist;
expect(reEncryptedKey).to.exist;
pgp.importKeys({
passphrase: 'yxcv',
privateKeyArmored: reEncryptedKey,
publicKeyArmored: pubkey
}, function(err) {
expect(err).to.not.exist;
done();
});
});
});
it('should work with empty passphrase', function(done) {
pgp.changePassphrase({
privateKeyArmored: privkey,
oldPassphrase: passphrase,
newPassphrase: undefined
}, function(err, reEncryptedKey) {
expect(err).to.not.exist;
expect(reEncryptedKey).to.exist;
pgp.importKeys({
passphrase: undefined,
privateKeyArmored: reEncryptedKey,
publicKeyArmored: pubkey
}, function(err) {
expect(err).to.not.exist;
done();
});
});
});
it('should fail when passphrases are equal', function(done) {
pgp.changePassphrase({
privateKeyArmored: privkey,
oldPassphrase: passphrase,
newPassphrase: passphrase
}, function(err, reEncryptedKey) {
expect(err).to.exist;
expect(reEncryptedKey).to.not.exist;
done();
});
});
it('should fail when old passphrase is incorrect', function(done) {
pgp.changePassphrase({
privateKeyArmored: privkey,
oldPassphrase: 'asd',
newPassphrase: 'yxcv'
}, function(err, reEncryptedKey) {
expect(err).to.exist;
expect(reEncryptedKey).to.not.exist;
done();
});
});
});
describe('Encrypt/Sign/Decrypt/Verify', function() {
var message = 'asdfs\n\nThursday, Nov 21, 2013 7:38 PM asdf@example.com wrote:\n' +
'> asdf\n' +
'> \n' +
'> Thursday, Nov 21, 2013 7:32 PM asdf@example.com wrote:\n' +
'> > secret 3';
var wrongPubkey = '-----BEGIN PGP PUBLIC KEY BLOCK-----\r\nVersion: OpenPGP.js v.1.20131116\r\nComment: Whiteout Mail - http://whiteout.io\r\n\r\nxsBNBFKODs4BB/9iOF4THsjQMY+WEpT7ShgKxj4bHzRRaQkqczS4nZvP0U3g\r\nqeqCnbpagyeKXA+bhWFQW4GmXtgAoeD5PXs6AZYrw3tWNxLKu2Oe6Tp9K/XI\r\nxTMQ2wl4qZKDXHvuPsJ7cmgaWqpPyXtxA4zHHS3WrkI/6VzHAcI/y6x4szSB\r\nKgSuhI3hjh3s7TybUC1U6AfoQGx/S7e3WwlCOrK8GTClirN/2mCPRC5wuIft\r\nnkoMfA6jK8d2OPrJ63shy5cgwHOjQg/xuk46dNS7tkvGmbaa+X0PgqSKB+Hf\r\nYPPNS/ylg911DH9qa8BqYU2QpNh9jUKXSF+HbaOM+plWkCSAL7czV+R3ABEB\r\nAAHNLVdoaXRlb3V0IFVzZXIgPHNhZmV3aXRobWUudGVzdHVzZXJAZ21haWwu\r\nY29tPsLAXAQQAQgAEAUCUo4O2gkQ1/uT/N+/wjwAAN2cB/9gFRmAfvEQ2qz+\r\nWubmT2EsSSnjPMxzG4uyykFoa+TaZCWo2Xa2tQghmU103kEkQb1OEjRjpgwJ\r\nYX9Kghnl8DByM686L5AXnRyHP78qRJCLXSXl0AGicboUDp5sovaa4rswQceH\r\nvcdWgZ/mgHTRoiQeJddy9k+H6MPFiyFaVcFwegVsmpc+dCcC8yT+qh8ZIbyG\r\nRJU60PmKKN7LUusP+8DbSv39zCGJCBlVVKyA4MzdF5uM+sqTdXbKzOrT5DGd\r\nCZaox4s+w16Sq1rHzZKFWfQPfKLDB9pyA0ufCVRA3AF6BUi7G3ZqhZiHNhMP\r\nNvE45V/hS1PbZcfPVoUjE2qc1Ix1\r\n=7Wpe\r\n-----END PGP PUBLIC KEY BLOCK-----';
beforeEach(function(done) {
pgp.importKeys({
passphrase: passphrase,
privateKeyArmored: privkey,
publicKeyArmored: pubkey
}, function(err) {
expect(err).to.not.exist;
done();
});
});
describe('Get KeyId', function() {
it('should work without param', function() {
var keyId = pgp.getKeyId();
expect(keyId).to.equal('F6F60E9B42CDFF4C');
});
it('should work with param', function() {
var keyId = pgp.getKeyId(pubkey);
expect(keyId).to.equal('F6F60E9B42CDFF4C');
});
});
describe('Get Fingerprint', function() {
it('should work without param', function() {
var fingerprint = pgp.getFingerprint();
expect(fingerprint).to.equal('5856CEF789C3A307E8A1B976F6F60E9B42CDFF4C');
});
it('should work with param', function() {
var fingerprint = pgp.getFingerprint(pubkey);
expect(fingerprint).to.equal('5856CEF789C3A307E8A1B976F6F60E9B42CDFF4C');
});
});
describe('getKeyParams', function() {
it('should work with param', function() {
var params = pgp.getKeyParams(pubkey);
expect(params.fingerprint).to.equal('5856CEF789C3A307E8A1B976F6F60E9B42CDFF4C');
expect(params._id).to.equal("F6F60E9B42CDFF4C");
expect(params.bitSize).to.equal(keySize);
expect(params.userId).to.equal("whiteout.test@t-online.de");
expect(params.userIds[0].name).to.equal("Whiteout User");
expect(params.userIds[0].emailAddress).to.equal("whiteout.test@t-online.de");
expect(params.algorithm).to.equal("rsa_encrypt_sign");
});
it('should work without param', function() {
var params = pgp.getKeyParams();
expect(params.fingerprint).to.equal('5856CEF789C3A307E8A1B976F6F60E9B42CDFF4C');
expect(params._id).to.equal("F6F60E9B42CDFF4C");
expect(params.bitSize).to.equal(keySize);
expect(params.userId).to.equal("whiteout.test@t-online.de");
expect(params.userIds[0].name).to.equal("Whiteout User");
expect(params.userIds[0].emailAddress).to.equal("whiteout.test@t-online.de");
expect(params.algorithm).to.equal("rsa_encrypt_sign");
});
});
describe('extractPublicKey', function() {
it('should work', function() {
var pk = pgp.extractPublicKey(privkey);
expect(pk).to.exist;
expect(pk).to.contain('-----BEGIN PGP PUBLIC KEY BLOCK-----');
});
});
describe('Encrypt and sign', function() {
it('should fail', function(done) {
var input = null;
pgp.encrypt(input, [pubkey], function(err, ct) {
expect(err).to.exist;
expect(ct).to.not.exist;
done();
});
});
it('should work', function(done) {
pgp.encrypt(message, [pubkey], function(err, ct) {
expect(err).to.not.exist;
expect(ct).to.exist;
done();
});
});
it('should encrypt to myself if public keys are empty', function(done) {
pgp.encrypt(message, undefined, function(err, ct) {
expect(err).to.not.exist;
expect(ct).to.exist;
done();
});
});
});
describe('Decrypt and verify', function() {
var ciphertext;
beforeEach(function(done) {
pgp.encrypt(message, [pubkey], function(err, ct) {
expect(err).to.not.exist;
expect(ct).to.exist;
ciphertext = ct;
done();
});
});
it('should fail', function(done) {
var input = 'asdfa\rsdf';
pgp.decrypt(input, pubkey, function(err, pt) {
expect(err).to.exist;
expect(pt).to.not.exist;
done();
});
});
it('should work', function(done) {
pgp.decrypt(ciphertext, pubkey, function(err, pt, signValid) {
expect(err).to.not.exist;
expect(pt).to.equal(message);
expect(signValid).to.be.true;
done();
});
});
it('should work without signature', function(done) {
var ct = openpgp.encryptMessage([pgp._publicKey], message);
pgp.decrypt(ct, undefined, function(err, pt, signValid) {
expect(err).to.not.exist;
expect(pt).to.equal(message);
expect(signValid).to.be.undefined;
done();
});
});
it('should fail to verify if public keys are empty', function(done) {
// setup another public key so that signature verification fails
pgp._publicKey = openpgp.key.readArmored(wrongPubkey).keys[0];
pgp.decrypt(ciphertext, undefined, function(err, pt, signValid) {
expect(err).to.not.exist;
expect(pt).to.equal(message);
expect(signValid).to.be.null;
done();
});
});
it('should decrypt but signValid should be null for wrong public key', function(done) {
pgp.decrypt(ciphertext, wrongPubkey, function(err, pt, signValid) {
expect(err).to.not.exist;
expect(pt).to.equal(message);
expect(signValid).to.be.null;
done();
});
});
});
describe('Verify clearsigned message', function() {
var clearsigned;
beforeEach(function() {
clearsigned = openpgp.signClearMessage(pgp._privateKey, 'this is a clearsigned message');
});
it('should work', function(done) {
pgp.verifyClearSignedMessage(clearsigned, pubkey, function(err, signaturesValid) {
expect(err).to.not.exist;
expect(signaturesValid).to.be.true;
done();
});
});
it('should fail', function(done) {
pgp.verifyClearSignedMessage(clearsigned.replace('clearsigned', 'invalid'), pubkey, function(err, signaturesValid) {
expect(err).to.not.exist;
expect(signaturesValid).to.be.false;
done();
});
});
it('should be null for wrong public key', function(done) {
pgp.verifyClearSignedMessage(clearsigned, wrongPubkey, function(err, signaturesValid) {
expect(err).to.not.exist;
expect(signaturesValid).to.be.null;
done();
});
});
});
describe('Verify detached signature', function() {
var signedMessage, signature;
beforeEach(function() {
signedMessage = 'this is a signed message';
var clearsigned = openpgp.signClearMessage(pgp._privateKey, signedMessage);
var signatureHeader = '-----BEGIN PGP SIGNATURE-----';
signature = signatureHeader + clearsigned.split(signatureHeader).pop();
});
it('should work', function(done) {
pgp.verifySignedMessage(signedMessage, signature, pubkey, function(err, signaturesValid) {
expect(err).to.not.exist;
expect(signaturesValid).to.be.true;
done();
});
});
it('should fail', function(done) {
pgp.verifySignedMessage(signedMessage.replace('signed', 'invalid'), signature, pubkey, function(err, signaturesValid) {
expect(err).to.not.exist;
expect(signaturesValid).to.be.false;
done();
});
});
it('should be null for wrong public key', function(done) {
pgp.verifySignedMessage(signedMessage, signature, wrongPubkey, function(err, signaturesValid) {
expect(err).to.not.exist;
expect(signaturesValid).to.be.null;
done();
});
});
});
});
});

View File

@ -1,226 +1,222 @@
define(function(require) {
'use strict';
'use strict';
var RestDAO = require('js/dao/rest-dao'),
PrivateKeyDAO = require('js/dao/privatekey-dao'),
expect = chai.expect;
var RestDAO = require('../../src/js/dao/rest-dao'),
PrivateKeyDAO = require('../../src/js/dao/privatekey-dao');
describe('Private Key DAO unit tests', function() {
describe('Private Key DAO unit tests', function() {
var privkeyDao, restDaoStub,
emailAddress = 'test@example.com',
deviceName = 'iPhone Work';
var privkeyDao, restDaoStub,
emailAddress = 'test@example.com',
deviceName = 'iPhone Work';
beforeEach(function() {
restDaoStub = sinon.createStubInstance(RestDAO);
privkeyDao = new PrivateKeyDAO(restDaoStub);
});
beforeEach(function() {
restDaoStub = sinon.createStubInstance(RestDAO);
privkeyDao = new PrivateKeyDAO(restDaoStub);
});
afterEach(function() {});
afterEach(function() {});
describe('requestDeviceRegistration', function() {
it('should fail due to invalid args', function(done) {
privkeyDao.requestDeviceRegistration({}, function(err, sessionKey) {
expect(err).to.exist;
expect(sessionKey).to.not.exist;
done();
});
});
it('should work', function(done) {
restDaoStub.post.yields(null, {
encryptedRegSessionKey: 'asdf'
});
privkeyDao.requestDeviceRegistration({
userId: emailAddress,
deviceName: deviceName
}, function(err, sessionKey) {
expect(err).to.not.exist;
expect(sessionKey).to.exist;
done();
});
describe('requestDeviceRegistration', function() {
it('should fail due to invalid args', function(done) {
privkeyDao.requestDeviceRegistration({}, function(err, sessionKey) {
expect(err).to.exist;
expect(sessionKey).to.not.exist;
done();
});
});
describe('uploadDeviceSecret', function() {
it('should fail due to invalid args', function(done) {
privkeyDao.uploadDeviceSecret({}, function(err) {
expect(err).to.exist;
done();
});
it('should work', function(done) {
restDaoStub.post.yields(null, {
encryptedRegSessionKey: 'asdf'
});
it('should work', function(done) {
restDaoStub.put.yields();
privkeyDao.requestDeviceRegistration({
userId: emailAddress,
deviceName: deviceName
}, function(err, sessionKey) {
expect(err).to.not.exist;
expect(sessionKey).to.exist;
done();
});
});
});
privkeyDao.uploadDeviceSecret({
userId: emailAddress,
deviceName: deviceName,
encryptedDeviceSecret: 'asdf',
iv: 'iv'
}, function(err) {
expect(err).to.not.exist;
done();
});
describe('uploadDeviceSecret', function() {
it('should fail due to invalid args', function(done) {
privkeyDao.uploadDeviceSecret({}, function(err) {
expect(err).to.exist;
done();
});
});
describe('requestAuthSessionKey', function() {
it('should fail due to invalid args', function(done) {
privkeyDao.requestAuthSessionKey({}, function(err) {
expect(err).to.exist;
done();
});
it('should work', function(done) {
restDaoStub.put.yields();
privkeyDao.uploadDeviceSecret({
userId: emailAddress,
deviceName: deviceName,
encryptedDeviceSecret: 'asdf',
iv: 'iv'
}, function(err) {
expect(err).to.not.exist;
done();
});
});
});
it('should work', function(done) {
restDaoStub.post.withArgs(undefined, '/auth/user/' + emailAddress).yields();
privkeyDao.requestAuthSessionKey({
userId: emailAddress
}, function(err) {
expect(err).to.not.exist;
done();
});
describe('requestAuthSessionKey', function() {
it('should fail due to invalid args', function(done) {
privkeyDao.requestAuthSessionKey({}, function(err) {
expect(err).to.exist;
done();
});
});
describe('verifyAuthentication', function() {
it('should fail due to invalid args', function(done) {
privkeyDao.verifyAuthentication({}, function(err) {
expect(err).to.exist;
done();
});
it('should work', function(done) {
restDaoStub.post.withArgs(undefined, '/auth/user/' + emailAddress).yields();
privkeyDao.requestAuthSessionKey({
userId: emailAddress
}, function(err) {
expect(err).to.not.exist;
done();
});
});
});
it('should work', function(done) {
var sessionId = '1';
var options = {
userId: emailAddress,
sessionId: sessionId,
encryptedChallenge: 'asdf',
encryptedDeviceSecret: 'qwer',
iv: ' iv'
};
restDaoStub.put.withArgs(options, '/auth/user/' + emailAddress + '/session/' + sessionId).yields();
privkeyDao.verifyAuthentication(options, function(err) {
expect(err).to.not.exist;
done();
});
describe('verifyAuthentication', function() {
it('should fail due to invalid args', function(done) {
privkeyDao.verifyAuthentication({}, function(err) {
expect(err).to.exist;
done();
});
});
describe('upload', function() {
it('should fail due to invalid args', function(done) {
privkeyDao.upload({}, function(err) {
expect(err).to.exist;
done();
});
it('should work', function(done) {
var sessionId = '1';
var options = {
userId: emailAddress,
sessionId: sessionId,
encryptedChallenge: 'asdf',
encryptedDeviceSecret: 'qwer',
iv: ' iv'
};
restDaoStub.put.withArgs(options, '/auth/user/' + emailAddress + '/session/' + sessionId).yields();
privkeyDao.verifyAuthentication(options, function(err) {
expect(err).to.not.exist;
done();
});
});
});
it('should work', function(done) {
var options = {
_id: '12345',
userId: emailAddress,
encryptedPrivateKey: 'asdf',
sessionId: '1',
salt: 'salt',
iv: 'iv'
};
restDaoStub.post.withArgs(options, '/privatekey/user/' + emailAddress + '/session/' + options.sessionId).yields();
privkeyDao.upload(options, function(err) {
expect(err).to.not.exist;
done();
});
describe('upload', function() {
it('should fail due to invalid args', function(done) {
privkeyDao.upload({}, function(err) {
expect(err).to.exist;
done();
});
});
describe('requestDownload', function() {
it('should fail due to invalid args', function(done) {
privkeyDao.requestDownload({}, function(err) {
expect(err).to.exist;
done();
});
it('should work', function(done) {
var options = {
_id: '12345',
userId: emailAddress,
encryptedPrivateKey: 'asdf',
sessionId: '1',
salt: 'salt',
iv: 'iv'
};
restDaoStub.post.withArgs(options, '/privatekey/user/' + emailAddress + '/session/' + options.sessionId).yields();
privkeyDao.upload(options, function(err) {
expect(err).to.not.exist;
done();
});
});
});
it('should work', function(done) {
var keyId = '12345';
restDaoStub.get.withArgs({
uri: '/privatekey/user/' + emailAddress + '/key/' + keyId
}).yields();
privkeyDao.requestDownload({
userId: emailAddress,
keyId: keyId
}, function(err, found) {
expect(err).to.not.exist;
expect(found).to.be.true;
done();
});
describe('requestDownload', function() {
it('should fail due to invalid args', function(done) {
privkeyDao.requestDownload({}, function(err) {
expect(err).to.exist;
done();
});
});
describe('hasPrivateKey', function() {
it('should fail due to invalid args', function(done) {
privkeyDao.hasPrivateKey({}, function(err) {
expect(err).to.exist;
done();
});
it('should work', function(done) {
var keyId = '12345';
restDaoStub.get.withArgs({
uri: '/privatekey/user/' + emailAddress + '/key/' + keyId
}).yields();
privkeyDao.requestDownload({
userId: emailAddress,
keyId: keyId
}, function(err, found) {
expect(err).to.not.exist;
expect(found).to.be.true;
done();
});
});
});
it('should work', function(done) {
var keyId = '12345';
restDaoStub.get.withArgs({
uri: '/privatekey/user/' + emailAddress + '/key/' + keyId + '?ignoreRecovery=true'
}).yields();
privkeyDao.hasPrivateKey({
userId: emailAddress,
keyId: keyId
}, function(err, found) {
expect(err).to.not.exist;
expect(found).to.be.true;
done();
});
describe('hasPrivateKey', function() {
it('should fail due to invalid args', function(done) {
privkeyDao.hasPrivateKey({}, function(err) {
expect(err).to.exist;
done();
});
});
describe('download', function() {
it('should fail due to invalid args', function(done) {
privkeyDao.download({}, function(err) {
expect(err).to.exist;
done();
});
it('should work', function(done) {
var keyId = '12345';
restDaoStub.get.withArgs({
uri: '/privatekey/user/' + emailAddress + '/key/' + keyId + '?ignoreRecovery=true'
}).yields();
privkeyDao.hasPrivateKey({
userId: emailAddress,
keyId: keyId
}, function(err, found) {
expect(err).to.not.exist;
expect(found).to.be.true;
done();
});
});
});
it('should work', function(done) {
var key = {
_id: '12345'
};
restDaoStub.get.withArgs({
uri: '/privatekey/user/' + emailAddress + '/key/' + key._id + '/recovery/token'
}).yields();
privkeyDao.download({
userId: emailAddress,
keyId: key._id,
recoveryToken: 'token'
}, function(err) {
expect(err).to.not.exist;
done();
});
describe('download', function() {
it('should fail due to invalid args', function(done) {
privkeyDao.download({}, function(err) {
expect(err).to.exist;
done();
});
});
it('should work', function(done) {
var key = {
_id: '12345'
};
restDaoStub.get.withArgs({
uri: '/privatekey/user/' + emailAddress + '/key/' + key._id + '/recovery/token'
}).yields();
privkeyDao.download({
userId: emailAddress,
keyId: key._id,
recoveryToken: 'token'
}, function(err) {
expect(err).to.not.exist;
done();
});
});
});
});

View File

@ -1,286 +1,281 @@
define(function(require) {
'use strict';
'use strict';
var expect = chai.expect,
angular = require('angular'),
mocks = require('angularMocks'),
PrivateKeyUploadCtrl = require('js/controller/privatekey-upload'),
appController = require('js/app-controller'),
KeychainDAO = require('js/dao/keychain-dao'),
PGP = require('js/crypto/pgp');
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');
describe('Private Key Upload Controller unit test', function() {
var scope, location, ctrl,
origEmailDao, emailDaoMock,
origKeychain, keychainMock,
pgpStub,
emailAddress = 'fred@foo.com';
describe('Private Key Upload Controller unit test', function() {
var scope, location, ctrl,
origEmailDao, emailDaoMock,
origKeychain, keychainMock,
pgpStub,
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
}
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);
angular.module('login-privatekey-download-test', []);
mocks.module('login-privatekey-download-test');
mocks.inject(function($controller, $rootScope) {
scope = $rootScope.$new();
scope.state = {};
ctrl = $controller(PrivateKeyUploadCtrl, {
$location: location,
$scope: scope
});
done();
});
});
afterEach(function() {
// restore the app controller module
appController._keychain = origKeychain;
appController._emailDao = origEmailDao;
});
describe('checkServerForKey', function() {
var keyParams = {
userId: emailAddress,
_id: 'keyId',
};
it('should fail', function(done) {
pgpStub.getKeyParams.returns(keyParams);
keychainMock.hasPrivateKey.yields(42);
scope.onError = function(err) {
expect(err).to.exist;
expect(keychainMock.hasPrivateKey.calledOnce).to.be.true;
done();
};
origKeychain = appController._keychain;
appController._keychain = keychainMock = sinon.createStubInstance(KeychainDAO);
keychainMock._pgp = pgpStub = sinon.createStubInstance(PGP);
angular.module('login-privatekey-download-test', []);
mocks.module('login-privatekey-download-test');
mocks.inject(function($controller, $rootScope) {
scope = $rootScope.$new();
scope.state = {};
ctrl = $controller(PrivateKeyUploadCtrl, {
$location: location,
$scope: scope
});
scope.checkServerForKey();
});
it('should return true', function(done) {
pgpStub.getKeyParams.returns(keyParams);
keychainMock.hasPrivateKey.withArgs({
userId: keyParams.userId,
keyId: keyParams._id
}).yields(null, true);
scope.checkServerForKey(function(privateKeySynced) {
expect(privateKeySynced).to.be.true;
done();
});
});
afterEach(function() {
// restore the app controller module
appController._keychain = origKeychain;
appController._emailDao = origEmailDao;
it('should return undefined', function(done) {
pgpStub.getKeyParams.returns(keyParams);
keychainMock.hasPrivateKey.withArgs({
userId: keyParams.userId,
keyId: keyParams._id
}).yields(null, false);
scope.checkServerForKey(function(privateKeySynced) {
expect(privateKeySynced).to.be.undefined;
done();
});
});
});
describe('handlePaste', function() {
it('should work', function() {
scope.handlePaste({
clipboardData: {
getData: function(val) {
expect(val).to.equal('text/plain');
return '1qaz-2wsx-3edc-4rfv-5tgb-6yhn';
}
}
});
expect(scope.code0).to.equal('1qaz');
expect(scope.code1).to.equal('2wsx');
expect(scope.code2).to.equal('3edc');
expect(scope.code3).to.equal('4rfv');
expect(scope.code4).to.equal('5tgb');
expect(scope.code5).to.equal('6yhn');
});
});
describe('displayUploadUi', function() {
it('should work', function() {
// add some artifacts from a previous key input
scope.code0 = scope.code1 = scope.code2 = scope.code3 = scope.code4 = scope.code5 = 'asdasd';
scope.displayUploadUi();
expect(scope.step).to.equal(1);
expect(scope.code.length).to.equal(24);
// artifacts should be cleared
expect(scope.code0).to.be.empty;
expect(scope.code1).to.be.empty;
expect(scope.code2).to.be.empty;
expect(scope.code3).to.be.empty;
expect(scope.code4).to.be.empty;
expect(scope.code5).to.be.empty;
});
});
describe('verifyCode', function() {
it('should fail for wrong code', function() {
scope.code0 = 'b';
scope.code1 = 'b';
scope.code2 = 'b';
scope.code3 = 'b';
scope.code4 = 'b';
scope.code5 = 'b';
scope.code = 'aaaaaa';
scope.onError = function() {};
expect(scope.verifyCode()).to.be.false;
});
describe('checkServerForKey', function() {
var keyParams = {
userId: emailAddress,
_id: 'keyId',
it('should work', function() {
scope.code0 = 'a';
scope.code1 = 'a';
scope.code2 = 'a';
scope.code3 = 'a';
scope.code4 = 'a';
scope.code5 = 'a';
scope.code = 'aaaaaa';
scope.onError = function() {};
expect(scope.verifyCode()).to.be.false;
});
});
describe('setDeviceName', function() {
it('should work', function(done) {
keychainMock.setDeviceName.yields();
scope.setDeviceName(done);
});
});
describe('encryptAndUploadKey', function() {
it('should fail due to keychain.registerDevice', function(done) {
keychainMock.registerDevice.yields(42);
scope.onError = function(err) {
expect(err).to.exist;
expect(keychainMock.registerDevice.calledOnce).to.be.true;
done();
};
it('should fail', function(done) {
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();
});
it('should return true', function(done) {
pgpStub.getKeyParams.returns(keyParams);
keychainMock.hasPrivateKey.withArgs({
userId: keyParams.userId,
keyId: keyParams._id
}).yields(null, true);
scope.checkServerForKey(function(privateKeySynced) {
expect(privateKeySynced).to.be.true;
done();
});
});
it('should return undefined', function(done) {
pgpStub.getKeyParams.returns(keyParams);
keychainMock.hasPrivateKey.withArgs({
userId: keyParams.userId,
keyId: keyParams._id
}).yields(null, false);
scope.checkServerForKey(function(privateKeySynced) {
expect(privateKeySynced).to.be.undefined;
done();
});
});
scope.encryptAndUploadKey();
});
describe('handlePaste', function() {
it('should work', function() {
scope.handlePaste({
clipboardData: {
getData: function(val) {
expect(val).to.equal('text/plain');
return '1qaz-2wsx-3edc-4rfv-5tgb-6yhn';
}
}
});
it('should work', function(done) {
keychainMock.registerDevice.yields();
keychainMock.uploadPrivateKey.yields();
expect(scope.code0).to.equal('1qaz');
expect(scope.code1).to.equal('2wsx');
expect(scope.code2).to.equal('3edc');
expect(scope.code3).to.equal('4rfv');
expect(scope.code4).to.equal('5tgb');
expect(scope.code5).to.equal('6yhn');
scope.encryptAndUploadKey(function(err) {
expect(err).to.not.exist;
expect(keychainMock.registerDevice.calledOnce).to.be.true;
expect(keychainMock.uploadPrivateKey.calledOnce).to.be.true;
done();
});
});
});
describe('displayUploadUi', function() {
it('should work', function() {
// add some artifacts from a previous key input
scope.code0 = scope.code1 = scope.code2 = scope.code3 = scope.code4 = scope.code5 = 'asdasd';
scope.displayUploadUi();
expect(scope.step).to.equal(1);
expect(scope.code.length).to.equal(24);
// artifacts should be cleared
expect(scope.code0).to.be.empty;
expect(scope.code1).to.be.empty;
expect(scope.code2).to.be.empty;
expect(scope.code3).to.be.empty;
expect(scope.code4).to.be.empty;
expect(scope.code5).to.be.empty;
});
describe('goBack', function() {
it('should work', function() {
scope.step = 2;
scope.goBack();
expect(scope.step).to.equal(1);
});
describe('verifyCode', function() {
it('should fail for wrong code', function() {
scope.code0 = 'b';
scope.code1 = 'b';
scope.code2 = 'b';
scope.code3 = 'b';
scope.code4 = 'b';
scope.code5 = 'b';
scope.code = 'aaaaaa';
it('should not work for < 2', function() {
scope.step = 1;
scope.goBack();
expect(scope.step).to.equal(1);
});
});
scope.onError = function() {};
expect(scope.verifyCode()).to.be.false;
});
it('should work', function() {
scope.code0 = 'a';
scope.code1 = 'a';
scope.code2 = 'a';
scope.code3 = 'a';
scope.code4 = 'a';
scope.code5 = 'a';
scope.code = 'aaaaaa';
scope.onError = function() {};
expect(scope.verifyCode()).to.be.false;
});
describe('goForward', function() {
var verifyCodeStub, setDeviceNameStub, encryptAndUploadKeyStub;
beforeEach(function() {
verifyCodeStub = sinon.stub(scope, 'verifyCode');
setDeviceNameStub = sinon.stub(scope, 'setDeviceName');
encryptAndUploadKeyStub = sinon.stub(scope, 'encryptAndUploadKey');
});
afterEach(function() {
verifyCodeStub.restore();
setDeviceNameStub.restore();
encryptAndUploadKeyStub.restore();
});
describe('setDeviceName', function() {
it('should work', function(done) {
keychainMock.setDeviceName.yields();
scope.setDeviceName(done);
});
it('should work for < 2', function() {
scope.step = 1;
scope.goForward();
expect(scope.step).to.equal(2);
});
describe('encryptAndUploadKey', function() {
it('should fail due to keychain.registerDevice', function(done) {
keychainMock.registerDevice.yields(42);
scope.onError = function(err) {
expect(err).to.exist;
expect(keychainMock.registerDevice.calledOnce).to.be.true;
done();
};
scope.encryptAndUploadKey();
});
it('should work', function(done) {
keychainMock.registerDevice.yields();
keychainMock.uploadPrivateKey.yields();
scope.encryptAndUploadKey(function(err) {
expect(err).to.not.exist;
expect(keychainMock.registerDevice.calledOnce).to.be.true;
expect(keychainMock.uploadPrivateKey.calledOnce).to.be.true;
done();
});
});
it('should work for 2', function() {
verifyCodeStub.returns(true);
scope.step = 2;
scope.goForward();
expect(scope.step).to.equal(3);
});
describe('goBack', function() {
it('should work', function() {
scope.step = 2;
scope.goBack();
expect(scope.step).to.equal(1);
});
it('should not work for < 2', function() {
scope.step = 1;
scope.goBack();
expect(scope.step).to.equal(1);
});
it('should not work for 2 when code invalid', function() {
verifyCodeStub.returns(false);
scope.step = 2;
scope.goForward();
expect(scope.step).to.equal(2);
});
describe('goForward', function() {
var verifyCodeStub, setDeviceNameStub, encryptAndUploadKeyStub;
beforeEach(function() {
verifyCodeStub = sinon.stub(scope, 'verifyCode');
setDeviceNameStub = sinon.stub(scope, 'setDeviceName');
encryptAndUploadKeyStub = sinon.stub(scope, 'encryptAndUploadKey');
});
afterEach(function() {
verifyCodeStub.restore();
setDeviceNameStub.restore();
encryptAndUploadKeyStub.restore();
});
it('should fail for 3 due to error in setDeviceName', function(done) {
scope.step = 3;
setDeviceNameStub.yields(42);
it('should work for < 2', function() {
scope.step = 1;
scope.goForward();
expect(scope.step).to.equal(2);
});
it('should work for 2', function() {
verifyCodeStub.returns(true);
scope.step = 2;
scope.goForward();
scope.onError = function(err) {
expect(err).to.exist;
expect(scope.step).to.equal(3);
});
done();
};
it('should not work for 2 when code invalid', function() {
verifyCodeStub.returns(false);
scope.step = 2;
scope.goForward();
expect(scope.step).to.equal(2);
});
it('should fail for 3 due to error in setDeviceName', function(done) {
scope.step = 3;
setDeviceNameStub.yields(42);
scope.onError = function(err) {
expect(err).to.exist;
expect(scope.step).to.equal(3);
done();
};
scope.goForward();
});
it('should fail for 3 due to error in encryptAndUploadKey', function(done) {
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();
});
it('should work for 3', function(done) {
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();
});
scope.goForward();
});
it('should fail for 3 due to error in encryptAndUploadKey', function(done) {
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();
});
it('should work for 3', function(done) {
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();
});
});
});

View File

@ -1,165 +1,161 @@
define(function(require) {
'use strict';
'use strict';
var RestDAO = require('js/dao/rest-dao'),
PublicKeyDAO = require('js/dao/publickey-dao'),
expect = chai.expect;
var RestDAO = require('../../src/js/dao/rest-dao'),
PublicKeyDAO = require('../../src/js/dao/publickey-dao');
describe('Public Key DAO unit tests', function() {
describe('Public Key DAO unit tests', function() {
var pubkeyDao, restDaoStub;
var pubkeyDao, restDaoStub;
beforeEach(function() {
restDaoStub = sinon.createStubInstance(RestDAO);
pubkeyDao = new PublicKeyDAO(restDaoStub);
});
beforeEach(function() {
restDaoStub = sinon.createStubInstance(RestDAO);
pubkeyDao = new PublicKeyDAO(restDaoStub);
});
afterEach(function() {});
afterEach(function() {});
describe('get', function() {
it('should fail', function(done) {
restDaoStub.get.yields(42);
describe('get', function() {
it('should fail', function(done) {
restDaoStub.get.yields(42);
pubkeyDao.get('id', function(err, key) {
expect(err).to.exist;
expect(key).to.not.exist;
expect(restDaoStub.get.calledOnce).to.be.true;
done();
});
});
it('should work', function(done) {
restDaoStub.get.yields(null, {
_id: '12345',
publicKey: 'asdf'
});
pubkeyDao.get('id', function(err, key) {
expect(err).to.not.exist;
expect(key).to.exist;
expect(key._id).to.exist;
expect(key.publicKey).to.exist;
expect(restDaoStub.get.calledOnce).to.be.true;
done();
});
pubkeyDao.get('id', function(err, key) {
expect(err).to.exist;
expect(key).to.not.exist;
expect(restDaoStub.get.calledOnce).to.be.true;
done();
});
});
describe('verify', function() {
it('should fail', function(done) {
restDaoStub.get.yields(42);
pubkeyDao.verify('id', function(err) {
expect(err).to.exist;
done();
});
it('should work', function(done) {
restDaoStub.get.yields(null, {
_id: '12345',
publicKey: 'asdf'
});
it('should not error for 400', function(done) {
restDaoStub.get.yields({
code: 400
});
pubkeyDao.verify('id', function(err) {
expect(err).to.not.exist;
done();
});
pubkeyDao.get('id', function(err, key) {
expect(err).to.not.exist;
expect(key).to.exist;
expect(key._id).to.exist;
expect(key.publicKey).to.exist;
expect(restDaoStub.get.calledOnce).to.be.true;
done();
});
});
});
it('should work', function(done) {
var uuid = 'c621e328-8548-40a1-8309-adf1955e98a9';
restDaoStub.get.yields(null);
describe('verify', function() {
it('should fail', function(done) {
restDaoStub.get.yields(42);
pubkeyDao.verify(uuid, function(err) {
expect(err).to.not.exist;
expect(restDaoStub.get.calledWith(sinon.match(function(arg){
return arg.uri === '/verify/' + uuid && arg.type === 'text';
}))).to.be.true;
done();
});
pubkeyDao.verify('id', function(err) {
expect(err).to.exist;
done();
});
});
describe('get by userId', function() {
it('should fail', function(done) {
restDaoStub.get.yields(42);
pubkeyDao.getByUserId('userId', function(err, key) {
expect(err).to.exist;
expect(key).to.not.exist;
expect(restDaoStub.get.calledOnce).to.be.true;
done();
});
it('should not error for 400', function(done) {
restDaoStub.get.yields({
code: 400
});
it('should react to 404', function(done) {
restDaoStub.get.yields({
code: 404
});
pubkeyDao.getByUserId('userId', function(err, key) {
expect(err).to.not.exist;
expect(key).to.not.exist;
expect(restDaoStub.get.calledOnce).to.be.true;
done();
});
});
it('should return empty array', function(done) {
restDaoStub.get.yields(null, []);
pubkeyDao.getByUserId('userId', function(err, key) {
expect(err).to.not.exist;
expect(key).to.not.exist;
expect(restDaoStub.get.calledOnce).to.be.true;
done();
});
});
it('should work', function(done) {
restDaoStub.get.yields(null, [{
_id: '12345',
publicKey: 'asdf'
}]);
pubkeyDao.getByUserId('userId', function(err, key) {
expect(err).to.not.exist;
expect(key).to.exist;
expect(key._id).to.exist;
expect(key.publicKey).to.exist;
expect(restDaoStub.get.calledOnce).to.be.true;
done();
});
pubkeyDao.verify('id', function(err) {
expect(err).to.not.exist;
done();
});
});
describe('put', function() {
it('should fail', function(done) {
restDaoStub.put.yields();
it('should work', function(done) {
var uuid = 'c621e328-8548-40a1-8309-adf1955e98a9';
restDaoStub.get.yields(null);
pubkeyDao.put({
_id: '12345',
publicKey: 'asdf'
}, function(err) {
expect(err).to.not.exist;
expect(restDaoStub.put.calledOnce).to.be.true;
done();
});
pubkeyDao.verify(uuid, function(err) {
expect(err).to.not.exist;
expect(restDaoStub.get.calledWith(sinon.match(function(arg) {
return arg.uri === '/verify/' + uuid && arg.type === 'text';
}))).to.be.true;
done();
});
});
});
describe('get by userId', function() {
it('should fail', function(done) {
restDaoStub.get.yields(42);
pubkeyDao.getByUserId('userId', function(err, key) {
expect(err).to.exist;
expect(key).to.not.exist;
expect(restDaoStub.get.calledOnce).to.be.true;
done();
});
});
describe('remove', function() {
it('should fail', function(done) {
restDaoStub.remove.yields();
it('should react to 404', function(done) {
restDaoStub.get.yields({
code: 404
});
pubkeyDao.remove('12345', function(err) {
expect(err).to.not.exist;
expect(restDaoStub.remove.calledOnce).to.be.true;
done();
});
pubkeyDao.getByUserId('userId', function(err, key) {
expect(err).to.not.exist;
expect(key).to.not.exist;
expect(restDaoStub.get.calledOnce).to.be.true;
done();
});
});
it('should return empty array', function(done) {
restDaoStub.get.yields(null, []);
pubkeyDao.getByUserId('userId', function(err, key) {
expect(err).to.not.exist;
expect(key).to.not.exist;
expect(restDaoStub.get.calledOnce).to.be.true;
done();
});
});
it('should work', function(done) {
restDaoStub.get.yields(null, [{
_id: '12345',
publicKey: 'asdf'
}]);
pubkeyDao.getByUserId('userId', function(err, key) {
expect(err).to.not.exist;
expect(key).to.exist;
expect(key._id).to.exist;
expect(key.publicKey).to.exist;
expect(restDaoStub.get.calledOnce).to.be.true;
done();
});
});
});
describe('put', function() {
it('should fail', function(done) {
restDaoStub.put.yields();
pubkeyDao.put({
_id: '12345',
publicKey: 'asdf'
}, function(err) {
expect(err).to.not.exist;
expect(restDaoStub.put.calledOnce).to.be.true;
done();
});
});
});
describe('remove', function() {
it('should fail', function(done) {
restDaoStub.remove.yields();
pubkeyDao.remove('12345', function(err) {
expect(err).to.not.exist;
expect(restDaoStub.remove.calledOnce).to.be.true;
done();
});
});
});
});

View File

@ -1,197 +1,193 @@
define(function(require) {
'use strict';
'use strict';
var expect = chai.expect,
angular = require('angular'),
mocks = require('angularMocks'),
KeychainDAO = require('js/dao/keychain-dao'),
InvitationDAO = require('js/dao/invitation-dao'),
PGP = require('js/crypto/pgp'),
ReadCtrl = require('js/controller/read'),
OutboxBO = require('js/bo/outbox'),
appController = require('js/app-controller');
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');
describe('Read Controller unit test', function() {
var scope, ctrl,
origKeychain, keychainMock,
origInvitation, invitationMock,
origCrypto, cryptoMock,
origOutbox, outboxMock,
origEmailDao;
describe('Read Controller unit test', function() {
var scope, ctrl,
origKeychain, keychainMock,
origInvitation, invitationMock,
origCrypto, cryptoMock,
origOutbox, outboxMock,
origEmailDao;
beforeEach(function() {
origKeychain = appController._keychain;
appController._keychain = keychainMock = sinon.createStubInstance(KeychainDAO);
beforeEach(function() {
origKeychain = appController._keychain;
appController._keychain = keychainMock = sinon.createStubInstance(KeychainDAO);
origInvitation = appController._invitationDao;
appController._invitationDao = invitationMock = sinon.createStubInstance(InvitationDAO);
origInvitation = appController._invitationDao;
appController._invitationDao = invitationMock = sinon.createStubInstance(InvitationDAO);
origCrypto = appController._pgp;
appController._pgp = cryptoMock = sinon.createStubInstance(PGP);
origCrypto = appController._pgp;
appController._pgp = cryptoMock = sinon.createStubInstance(PGP);
origOutbox = appController._outboxBo;
appController._outboxBo = outboxMock = sinon.createStubInstance(OutboxBO);
origOutbox = appController._outboxBo;
appController._outboxBo = outboxMock = sinon.createStubInstance(OutboxBO);
origEmailDao = appController._emailDao;
appController._emailDao = {
_account: 'sender@example.com'
origEmailDao = appController._emailDao;
appController._emailDao = {
_account: 'sender@example.com'
};
angular.module('readtest', []);
mocks.module('readtest');
mocks.inject(function($rootScope, $controller) {
scope = $rootScope.$new();
scope.state = {};
ctrl = $controller(ReadCtrl, {
$scope: scope
});
});
});
afterEach(function() {
appController._keychain = origKeychain;
appController._invitationDao = origInvitation;
appController._pgp = origCrypto;
appController._outboxBo = origOutbox;
appController._emailDao = origEmailDao;
});
describe('scope variables', function() {
it('should be set correctly', function() {
expect(scope.state.read).to.exist;
expect(scope.state.read.open).to.be.false;
expect(scope.state.read.toggle).to.exist;
});
});
describe('open/close read view', function() {
it('should open/close', function() {
expect(scope.state.read.open).to.be.false;
scope.state.read.toggle(true);
expect(scope.state.read.open).to.be.true;
scope.state.read.toggle(false);
expect(scope.state.read.open).to.be.false;
});
});
describe('getKeyId', function() {
var address = 'asfd@asdf.com';
it('should show searching on error', 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...');
};
angular.module('readtest', []);
mocks.module('readtest');
mocks.inject(function($rootScope, $controller) {
scope = $rootScope.$new();
scope.state = {};
ctrl = $controller(ReadCtrl, {
$scope: scope
});
});
scope.getKeyId(address);
});
afterEach(function() {
appController._keychain = origKeychain;
appController._invitationDao = origInvitation;
appController._pgp = origCrypto;
appController._outboxBo = origOutbox;
appController._emailDao = origEmailDao;
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);
});
describe('scope variables', function() {
it('should be set correctly', function() {
expect(scope.state.read).to.exist;
expect(scope.state.read.open).to.be.false;
expect(scope.state.read.toggle).to.exist;
it('should show searching on error', function() {
keychainMock.getReceiverPublicKey.yields(null, {
publicKey: 'PUBLIC KEY'
});
cryptoMock.getFingerprint.returns('XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX');
scope.onError = function(err) {
expect(err).to.not.exist;
expect(scope.keyId).to.equal('PGP key: XXXXXXXX');
};
scope.getKeyId(address);
});
describe('open/close read view', function() {
it('should open/close', function() {
expect(scope.state.read.open).to.be.false;
scope.state.read.toggle(true);
expect(scope.state.read.open).to.be.true;
scope.state.read.toggle(false);
expect(scope.state.read.open).to.be.false;
});
});
describe('getKeyId', function() {
var address = 'asfd@asdf.com';
it('should show searching on error', 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);
});
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);
});
it('should show searching on error', function() {
keychainMock.getReceiverPublicKey.yields(null, {
publicKey: 'PUBLIC KEY'
});
cryptoMock.getFingerprint.returns('XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX');
scope.onError = function(err) {
expect(err).to.not.exist;
expect(scope.keyId).to.equal('PGP key: XXXXXXXX');
};
scope.getKeyId(address);
});
});
describe('invite', function() {
it('not allow invitation for secure users', function() {
expect(scope.keyId).to.equal('No key found.');
scope.invite({
secure: true,
address: 'asdf@asdf.de'
});
expect(scope.keyId).to.equal('No key found.');
});
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'
});
});
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'
});
});
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'
});
});
});
describe('parseConversation', function() {
it.skip('should work', function() {
var body = 'foo\n' +
'\n' +
'> bar\n' +
'>\n' +
'> foofoo\n' +
'>> foofoobar\n' +
'\ncomment\n' +
'>> barbar';
var nodes = scope.parseConversation({
body: body
});
expect(nodes).to.exist;
var expectedJson = '{"children":["foo\\n",{"children":["bar\\n\\nfoofoo",{"children":["foofoobar"]}]},"\\ncomment",{"children":[{"children":["barbar"]}]}]}';
var json = JSON.stringify(nodes);
expect(json).to.equal(expectedJson);
var expectedHtml = '<div class="view-read-body"><div class="line"><span>foo</span><br></div><div class="line empty-line"><span></span><br></div><div class="prev-message"><div class="line"><span>bar</span><br></div><div class="line empty-line"><span></span><br></div><div class="line"><span>foofoo</span><br></div></div><div class="prev-message"><div class="prev-message"><div class="line"><span>foofoobar</span><br></div></div></div><div class="line empty-line"><span></span><br></div><div class="line"><span>comment</span><br></div><div class="prev-message"><div class="prev-message"><div class="line"><span>barbar</span><br></div></div></div></div>';
var html = scope.renderNodes(nodes);
expect(html).to.equal(expectedHtml);
});
});
});
describe('invite', function() {
it('not allow invitation for secure users', function() {
expect(scope.keyId).to.equal('No key found.');
scope.invite({
secure: true,
address: 'asdf@asdf.de'
});
expect(scope.keyId).to.equal('No key found.');
});
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'
});
});
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'
});
});
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'
});
});
});
describe('parseConversation', function() {
it.skip('should work', function() {
var body = 'foo\n' +
'\n' +
'> bar\n' +
'>\n' +
'> foofoo\n' +
'>> foofoobar\n' +
'\ncomment\n' +
'>> barbar';
var nodes = scope.parseConversation({
body: body
});
expect(nodes).to.exist;
var expectedJson = '{"children":["foo\\n",{"children":["bar\\n\\nfoofoo",{"children":["foofoobar"]}]},"\\ncomment",{"children":[{"children":["barbar"]}]}]}';
var json = JSON.stringify(nodes);
expect(json).to.equal(expectedJson);
var expectedHtml = '<div class="view-read-body"><div class="line"><span>foo</span><br></div><div class="line empty-line"><span></span><br></div><div class="prev-message"><div class="line"><span>bar</span><br></div><div class="line empty-line"><span></span><br></div><div class="line"><span>foofoo</span><br></div></div><div class="prev-message"><div class="prev-message"><div class="line"><span>foofoobar</span><br></div></div></div><div class="line empty-line"><span></span><br></div><div class="line"><span>comment</span><br></div><div class="prev-message"><div class="prev-message"><div class="line"><span>barbar</span><br></div></div></div></div>';
var html = scope.renderNodes(nodes);
expect(html).to.equal(expectedHtml);
});
});
});

View File

@ -1,225 +1,221 @@
define(function(require) {
'use strict';
'use strict';
var RestDAO = require('js/dao/rest-dao'),
expect = chai.expect;
var RestDAO = require('../../src/js/dao/rest-dao');
describe('Rest DAO unit tests', function() {
describe('Rest DAO unit tests', function() {
var restDao, xhrMock, requests;
var restDao, xhrMock, requests;
beforeEach(function() {
beforeEach(function() {
restDao = new RestDAO();
xhrMock = sinon.useFakeXMLHttpRequest();
requests = [];
xhrMock.onCreate = function(xhr) {
requests.push(xhr);
};
});
afterEach(function() {
xhrMock.restore();
});
describe('contructor', function() {
it('should set default base uri', function() {
restDao = new RestDAO();
xhrMock = sinon.useFakeXMLHttpRequest();
requests = [];
xhrMock.onCreate = function(xhr) {
requests.push(xhr);
};
expect(restDao).to.exist;
expect(restDao._baseUri).to.exist;
});
afterEach(function() {
xhrMock.restore();
});
it('should accept default base uri', function() {
var baseUri = 'http://custom.com';
describe('contructor', function() {
it('should set default base uri', function() {
restDao = new RestDAO();
expect(restDao).to.exist;
expect(restDao._baseUri).to.exist;
restDao = new RestDAO(baseUri);
expect(restDao).to.exist;
expect(restDao._baseUri).to.equal(baseUri);
});
});
describe('get', function() {
it('should work with json as default type', function() {
restDao.get({
uri: '/asdf'
}, function(err, data, status) {
expect(err).to.not.exist;
expect(data.foo).to.equal('bar');
var req = requests[0];
expect(req.requestHeaders.Accept).to.equal('application/json');
expect(status).to.equal(200);
});
it('should accept default base uri', function() {
var baseUri = 'http://custom.com';
expect(requests.length).to.equal(1);
requests[0].respond(200, {
"Content-Type": "application/json"
}, '{"foo": "bar"}');
});
restDao = new RestDAO(baseUri);
expect(restDao).to.exist;
expect(restDao._baseUri).to.equal(baseUri);
it('should work with jsonz', function() {
restDao.get({
uri: '/asdf',
type: 'json'
}, function(err, data, status) {
expect(err).to.not.exist;
expect(data.foo).to.equal('bar');
var req = requests[0];
expect(req.requestHeaders.Accept).to.equal('application/json');
expect(status).to.equal(200);
});
expect(requests.length).to.equal(1);
requests[0].respond(200, {
"Content-Type": "application/json"
}, '{"foo": "bar"}');
});
it('should work with plain text', function() {
restDao.get({
uri: '/asdf',
type: 'text'
}, function(err, data, status) {
expect(err).to.not.exist;
expect(data).to.equal('foobar!');
var req = requests[0];
expect(req.requestHeaders.Accept).to.equal('text/plain');
expect(status).to.equal(200);
});
expect(requests.length).to.equal(1);
requests[0].respond(200, {
"Content-Type": "text/plain"
}, 'foobar!');
});
it('should work with xml', function() {
restDao.get({
uri: '/asdf',
type: 'xml'
}, function(err, data, status) {
expect(err).to.not.exist;
expect(data).to.equal('<foo>bar</foo>');
var req = requests[0];
expect(req.requestHeaders.Accept).to.equal('application/xml');
expect(status).to.equal(200);
});
expect(requests.length).to.equal(1);
requests[0].respond(200, {
"Content-Type": "application/xml"
}, '<foo>bar</foo>');
});
it('should fail for missing uri parameter', function() {
restDao.get({}, function(err, data) {
expect(err).to.exist;
expect(err.code).to.equal(400);
expect(data).to.not.exist;
});
});
describe('get', function() {
it('should work with json as default type', function() {
restDao.get({
uri: '/asdf'
}, function(err, data, status) {
expect(err).to.not.exist;
expect(data.foo).to.equal('bar');
var req = requests[0];
expect(req.requestHeaders.Accept).to.equal('application/json');
expect(status).to.equal(200);
});
expect(requests.length).to.equal(1);
requests[0].respond(200, {
"Content-Type": "application/json"
}, '{"foo": "bar"}');
});
it('should work with jsonz', function() {
restDao.get({
uri: '/asdf',
type: 'json'
}, function(err, data, status) {
expect(err).to.not.exist;
expect(data.foo).to.equal('bar');
var req = requests[0];
expect(req.requestHeaders.Accept).to.equal('application/json');
expect(status).to.equal(200);
});
expect(requests.length).to.equal(1);
requests[0].respond(200, {
"Content-Type": "application/json"
}, '{"foo": "bar"}');
});
it('should work with plain text', function() {
restDao.get({
uri: '/asdf',
type: 'text'
}, function(err, data, status) {
expect(err).to.not.exist;
expect(data).to.equal('foobar!');
var req = requests[0];
expect(req.requestHeaders.Accept).to.equal('text/plain');
expect(status).to.equal(200);
});
expect(requests.length).to.equal(1);
requests[0].respond(200, {
"Content-Type": "text/plain"
}, 'foobar!');
});
it('should work with xml', function() {
restDao.get({
uri: '/asdf',
type: 'xml'
}, function(err, data, status) {
expect(err).to.not.exist;
expect(data).to.equal('<foo>bar</foo>');
var req = requests[0];
expect(req.requestHeaders.Accept).to.equal('application/xml');
expect(status).to.equal(200);
});
expect(requests.length).to.equal(1);
requests[0].respond(200, {
"Content-Type": "application/xml"
}, '<foo>bar</foo>');
});
it('should fail for missing uri parameter', function() {
restDao.get({}, function(err, data) {
expect(err).to.exist;
expect(err.code).to.equal(400);
expect(data).to.not.exist;
});
});
it('should fail for unhandled data type', function() {
restDao.get({
uri: '/asdf',
type: 'snafu'
}, function(err, data) {
expect(err).to.exist;
expect(err.code).to.equal(400);
expect(data).to.not.exist;
});
});
it('should fail for server error', function() {
restDao.get({
uri: '/asdf'
}, function(err, data) {
expect(err).to.exist;
expect(err.code).to.equal(500);
expect(data).to.not.exist;
});
expect(requests.length).to.equal(1);
requests[0].respond(500, {
"Content-Type": "text/plain"
}, 'Internal error');
it('should fail for unhandled data type', function() {
restDao.get({
uri: '/asdf',
type: 'snafu'
}, function(err, data) {
expect(err).to.exist;
expect(err.code).to.equal(400);
expect(data).to.not.exist;
});
});
describe('post', function() {
it('should fail', function() {
restDao.post('/asdf', {}, function(err) {
expect(err).to.exist;
expect(err.code).to.equal(500);
});
expect(requests.length).to.equal(1);
requests[0].respond(500, {
"Content-Type": "text/plain"
}, 'Internal error');
it('should fail for server error', function() {
restDao.get({
uri: '/asdf'
}, function(err, data) {
expect(err).to.exist;
expect(err.code).to.equal(500);
expect(data).to.not.exist;
});
it('should work', function() {
restDao.post('/asdf', {}, function(err, res, status) {
expect(err).to.not.exist;
expect(res).to.equal('');
expect(status).to.equal(201);
});
expect(requests.length).to.equal(1);
requests[0].respond(500, {
"Content-Type": "text/plain"
}, 'Internal error');
});
});
expect(requests.length).to.equal(1);
requests[0].respond(201);
describe('post', function() {
it('should fail', function() {
restDao.post('/asdf', {}, function(err) {
expect(err).to.exist;
expect(err.code).to.equal(500);
});
expect(requests.length).to.equal(1);
requests[0].respond(500, {
"Content-Type": "text/plain"
}, 'Internal error');
});
describe('put', function() {
it('should fail', function() {
restDao.put('/asdf', {}, function(err) {
expect(err).to.exist;
expect(err.code).to.equal(500);
});
expect(requests.length).to.equal(1);
requests[0].respond(500, {
"Content-Type": "text/plain"
}, 'Internal error');
it('should work', function() {
restDao.post('/asdf', {}, function(err, res, status) {
expect(err).to.not.exist;
expect(res).to.equal('');
expect(status).to.equal(201);
});
it('should work', function() {
restDao.put('/asdf', {}, function(err, res, status) {
expect(err).to.not.exist;
expect(res).to.equal('');
expect(status).to.equal(201);
});
expect(requests.length).to.equal(1);
requests[0].respond(201);
});
});
expect(requests.length).to.equal(1);
requests[0].respond(201);
describe('put', function() {
it('should fail', function() {
restDao.put('/asdf', {}, function(err) {
expect(err).to.exist;
expect(err.code).to.equal(500);
});
expect(requests.length).to.equal(1);
requests[0].respond(500, {
"Content-Type": "text/plain"
}, 'Internal error');
});
describe('remove', function() {
it('should fail', function() {
restDao.remove('/asdf', function(err) {
expect(err).to.exist;
expect(err.code).to.equal(500);
});
expect(requests.length).to.equal(1);
requests[0].respond(500, {
"Content-Type": "text/plain"
}, 'Internal error');
it('should work', function() {
restDao.put('/asdf', {}, function(err, res, status) {
expect(err).to.not.exist;
expect(res).to.equal('');
expect(status).to.equal(201);
});
it('should work', function() {
restDao.remove('/asdf', function(err, res, status) {
expect(err).to.not.exist;
expect(res).to.equal('');
expect(status).to.equal(200);
});
expect(requests.length).to.equal(1);
requests[0].respond(201);
});
});
expect(requests.length).to.equal(1);
requests[0].respond(200);
describe('remove', function() {
it('should fail', function() {
restDao.remove('/asdf', function(err) {
expect(err).to.exist;
expect(err.code).to.equal(500);
});
expect(requests.length).to.equal(1);
requests[0].respond(500, {
"Content-Type": "text/plain"
}, 'Internal error');
});
it('should work', function() {
restDao.remove('/asdf', function(err, res, status) {
expect(err).to.not.exist;
expect(res).to.equal('');
expect(status).to.equal(200);
});
expect(requests.length).to.equal(1);
requests[0].respond(200);
});
});
});

View File

@ -1,126 +1,122 @@
define(function(require) {
'use strict';
'use strict';
var expect = chai.expect,
angular = require('angular'),
mocks = require('angularMocks'),
SetPassphraseCtrl = require('js/controller/set-passphrase'),
PGP = require('js/crypto/pgp'),
appController = require('js/app-controller'),
KeychainDAO = require('js/dao/keychain-dao');
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');
describe('Set Passphrase Controller unit test', function() {
var scope, setPassphraseCtrl,
dummyFingerprint, expectedFingerprint,
dummyKeyId, expectedKeyId,
emailAddress, keySize, cryptoMock, keychainMock;
describe('Set Passphrase Controller unit test', function() {
var scope, setPassphraseCtrl,
dummyFingerprint, expectedFingerprint,
dummyKeyId, expectedKeyId,
emailAddress, keySize, cryptoMock, keychainMock;
beforeEach(function() {
appController._pgp = cryptoMock = sinon.createStubInstance(PGP);
appController._keychain = keychainMock = sinon.createStubInstance(KeychainDAO);
beforeEach(function() {
appController._pgp = cryptoMock = sinon.createStubInstance(PGP);
appController._keychain = keychainMock = sinon.createStubInstance(KeychainDAO);
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);
emailAddress = 'fred@foo.com';
keySize = 1234;
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);
emailAddress = 'fred@foo.com';
keySize = 1234;
cryptoMock.getKeyParams.returns({
cryptoMock.getKeyParams.returns({
_id: dummyKeyId,
fingerprint: dummyFingerprint,
userId: emailAddress,
userIds: [],
bitSize: keySize
});
angular.module('setpassphrasetest', []);
mocks.module('setpassphrasetest');
mocks.inject(function($rootScope, $controller) {
scope = $rootScope.$new();
scope.state = {};
setPassphraseCtrl = $controller(SetPassphraseCtrl, {
$scope: scope
});
});
});
afterEach(function() {});
describe('setPassphrase', function() {
it('should work', function(done) {
scope.oldPassphrase = 'old';
scope.newPassphrase = 'new';
keychainMock.lookupPrivateKey.withArgs(dummyKeyId).yields(null, {
encryptedKey: 'encrypted'
});
cryptoMock.changePassphrase.withArgs({
privateKeyArmored: 'encrypted',
oldPassphrase: 'old',
newPassphrase: 'new'
}).yields(null, 'newArmoredKey');
keychainMock.saveLocalPrivateKey.withArgs({
_id: dummyKeyId,
fingerprint: dummyFingerprint,
userId: emailAddress,
userIds: [],
bitSize: keySize
});
encryptedKey: 'newArmoredKey'
}).yields();
angular.module('setpassphrasetest', []);
mocks.module('setpassphrasetest');
mocks.inject(function($rootScope, $controller) {
scope = $rootScope.$new();
scope.state = {};
setPassphraseCtrl = $controller(SetPassphraseCtrl, {
$scope: scope
});
});
scope.onError = function(err) {
expect(err.title).to.equal('Success');
done();
};
scope.setPassphrase();
});
afterEach(function() {});
describe('setPassphrase', function() {
it('should work', function(done) {
scope.oldPassphrase = 'old';
scope.newPassphrase = 'new';
keychainMock.lookupPrivateKey.withArgs(dummyKeyId).yields(null, {
encryptedKey: 'encrypted'
});
cryptoMock.changePassphrase.withArgs({
privateKeyArmored: 'encrypted',
oldPassphrase: 'old',
newPassphrase: 'new'
}).yields(null, 'newArmoredKey');
keychainMock.saveLocalPrivateKey.withArgs({
_id: dummyKeyId,
userId: emailAddress,
userIds: [],
encryptedKey: 'newArmoredKey'
}).yields();
scope.onError = function(err) {
expect(err.title).to.equal('Success');
done();
};
scope.setPassphrase();
});
});
describe('check passphrase quality', function() {
it('should be too short', function() {
scope.newPassphrase = '&§DG36';
scope.checkPassphraseQuality();
expect(scope.passphraseMsg).to.equal('Very weak');
expect(scope.passphraseRating).to.equal(0);
});
it('should be very weak', function() {
scope.newPassphrase = 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa';
scope.checkPassphraseQuality();
expect(scope.passphraseMsg).to.equal('Very weak');
expect(scope.passphraseRating).to.equal(0);
});
it('should be weak', function() {
scope.newPassphrase = 'asdfasdfasdfasdfasdfasdfasdfasdfasdfasdfasdf';
scope.checkPassphraseQuality();
expect(scope.passphraseMsg).to.equal('Weak');
expect(scope.passphraseRating).to.equal(1);
});
it('should be good', function() {
scope.newPassphrase = 'asdfasdfasdfasdfasdfasdfasdfasdfasdfasdfasdf5';
scope.checkPassphraseQuality();
expect(scope.passphraseMsg).to.equal('Good');
expect(scope.passphraseRating).to.equal(2);
});
it('should be strong', function() {
scope.newPassphrase = '&§DG36abcd';
scope.checkPassphraseQuality();
expect(scope.passphraseMsg).to.equal('Strong');
expect(scope.passphraseRating).to.equal(3);
});
});
});
describe('check passphrase quality', function() {
it('should be too short', function() {
scope.newPassphrase = '&§DG36';
scope.checkPassphraseQuality();
expect(scope.passphraseMsg).to.equal('Very weak');
expect(scope.passphraseRating).to.equal(0);
});
it('should be very weak', function() {
scope.newPassphrase = 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa';
scope.checkPassphraseQuality();
expect(scope.passphraseMsg).to.equal('Very weak');
expect(scope.passphraseRating).to.equal(0);
});
it('should be weak', function() {
scope.newPassphrase = 'asdfasdfasdfasdfasdfasdfasdfasdfasdfasdfasdf';
scope.checkPassphraseQuality();
expect(scope.passphraseMsg).to.equal('Weak');
expect(scope.passphraseRating).to.equal(1);
});
it('should be good', function() {
scope.newPassphrase = 'asdfasdfasdfasdfasdfasdfasdfasdfasdfasdfasdf5';
scope.checkPassphraseQuality();
expect(scope.passphraseMsg).to.equal('Good');
expect(scope.passphraseRating).to.equal(2);
});
it('should be strong', function() {
scope.newPassphrase = '&§DG36abcd';
scope.checkPassphraseQuality();
expect(scope.passphraseMsg).to.equal('Strong');
expect(scope.passphraseRating).to.equal(3);
});
});
});

View File

@ -1,472 +1,469 @@
define(function(require) {
'use strict';
'use strict';
var DeviceStorageDAO = require('js/dao/devicestorage-dao'),
Auth = require('js/bo/auth'),
cfg = require('js/app-config').config,
UpdateHandler = require('js/util/update/update-handler'),
config = require('js/app-config').config,
expect = chai.expect;
var DeviceStorageDAO = require('../../src/js/dao/devicestorage-dao'),
Auth = require('../../src/js/bo/auth'),
cfg = require('../../src/js/app-config').config,
UpdateHandler = require('../../src/js/util/update/update-handler'),
config = require('../../src/js/app-config').config;
describe('UpdateHandler', function() {
var updateHandler, appConfigStorageStub, authStub, userStorageStub, origDbVersion;
describe('UpdateHandler', function() {
var updateHandler, appConfigStorageStub, authStub, userStorageStub, origDbVersion;
chai.Assertion.includeStack = true;
chai.Assertion.includeStack = true;
beforeEach(function() {
origDbVersion = cfg.dbVersion;
appConfigStorageStub = sinon.createStubInstance(DeviceStorageDAO);
userStorageStub = sinon.createStubInstance(DeviceStorageDAO);
authStub = sinon.createStubInstance(Auth);
updateHandler = new UpdateHandler(appConfigStorageStub, userStorageStub, authStub);
beforeEach(function() {
origDbVersion = cfg.dbVersion;
appConfigStorageStub = sinon.createStubInstance(DeviceStorageDAO);
userStorageStub = sinon.createStubInstance(DeviceStorageDAO);
authStub = sinon.createStubInstance(Auth);
updateHandler = new UpdateHandler(appConfigStorageStub, userStorageStub, authStub);
});
afterEach(function() {
cfg.dbVersion = origDbVersion;
});
describe('#constructor', function() {
it('should create instance', function() {
expect(updateHandler).to.exist;
expect(updateHandler._appConfigStorage).to.equal(appConfigStorageStub);
expect(updateHandler._userStorage).to.equal(userStorageStub);
// the update handler must contain as many db update sripts as there are database versions
expect(updateHandler._updateScripts.length).to.equal(cfg.dbVersion);
});
});
afterEach(function() {
cfg.dbVersion = origDbVersion;
});
describe('#update', function() {
var versionDbType = 'dbVersion';
describe('#constructor', function() {
it('should create instance', function() {
expect(updateHandler).to.exist;
expect(updateHandler._appConfigStorage).to.equal(appConfigStorageStub);
expect(updateHandler._userStorage).to.equal(userStorageStub);
it('should not update when up to date', function(done) {
cfg.dbVersion = 10; // app requires database version 10
appConfigStorageStub.listItems.withArgs(versionDbType).yieldsAsync(null, ['10']); // database version is 10
// the update handler must contain as many db update sripts as there are database versions
expect(updateHandler._updateScripts.length).to.equal(cfg.dbVersion);
updateHandler.update(function(error) {
expect(error).to.not.exist;
expect(appConfigStorageStub.listItems.calledOnce).to.be.true;
done();
});
});
describe('#update', function() {
var versionDbType = 'dbVersion';
describe('dummy updates for v2 to v4', function() {
var updateCounter;
it('should not update when up to date', function(done) {
cfg.dbVersion = 10; // app requires database version 10
appConfigStorageStub.listItems.withArgs(versionDbType).yieldsAsync(null, ['10']); // database version is 10
beforeEach(function() {
updateCounter = 0;
appConfigStorageStub.listItems.withArgs(versionDbType).yieldsAsync(null, ['2']); // database version is 0
});
afterEach(function() {
expect(appConfigStorageStub.listItems.calledOnce).to.be.true;
});
it('should work', function(done) {
cfg.dbVersion = 4; // app requires database version 4
// a simple dummy update to executed that only increments the update counter
function dummyUpdate(options, callback) {
updateCounter++;
callback();
}
// inject the dummy updates instead of live ones
updateHandler._updateScripts = [dummyUpdate, dummyUpdate, dummyUpdate, dummyUpdate];
// execute test
updateHandler.update(function(error) {
expect(error).to.not.exist;
expect(appConfigStorageStub.listItems.calledOnce).to.be.true;
expect(updateCounter).to.equal(2);
done();
});
});
describe('dummy updates for v2 to v4', function() {
var updateCounter;
it('should fail while updating to v3', function(done) {
cfg.dbVersion = 4; // app requires database version 4
beforeEach(function() {
updateCounter = 0;
appConfigStorageStub.listItems.withArgs(versionDbType).yieldsAsync(null, ['2']); // database version is 0
});
function dummyUpdate(options, callback) {
updateCounter++;
callback();
}
afterEach(function() {
expect(appConfigStorageStub.listItems.calledOnce).to.be.true;
});
function failingUpdate(options, callback) {
callback({});
}
// inject the dummy updates instead of live ones
updateHandler._updateScripts = [dummyUpdate, dummyUpdate, failingUpdate, dummyUpdate];
it('should work', function(done) {
cfg.dbVersion = 4; // app requires database version 4
// execute test
updateHandler.update(function(error) {
expect(error).to.exist;
expect(updateCounter).to.equal(0);
// a simple dummy update to executed that only increments the update counter
function dummyUpdate(options, callback) {
updateCounter++;
callback();
}
// inject the dummy updates instead of live ones
updateHandler._updateScripts = [dummyUpdate, dummyUpdate, dummyUpdate, dummyUpdate];
// execute test
updateHandler.update(function(error) {
expect(error).to.not.exist;
expect(updateCounter).to.equal(2);
done();
});
});
it('should fail while updating to v3', function(done) {
cfg.dbVersion = 4; // app requires database version 4
function dummyUpdate(options, callback) {
updateCounter++;
callback();
}
function failingUpdate(options, callback) {
callback({});
}
// inject the dummy updates instead of live ones
updateHandler._updateScripts = [dummyUpdate, dummyUpdate, failingUpdate, dummyUpdate];
// execute test
updateHandler.update(function(error) {
expect(error).to.exist;
expect(updateCounter).to.equal(0);
done();
});
});
});
describe('v0 -> v1', function() {
var emailDbType = 'email_';
beforeEach(function() {
cfg.dbVersion = 1; // app requires database version 1
appConfigStorageStub.listItems.withArgs(versionDbType).yieldsAsync(); // database version is 0
});
afterEach(function() {
// database version is only queried for version checking prior to the update script
// so no need to check this in case-specific tests
expect(appConfigStorageStub.listItems.calledOnce).to.be.true;
});
it('should work', function(done) {
userStorageStub.removeList.withArgs(emailDbType).yieldsAsync();
appConfigStorageStub.storeList.withArgs([1], versionDbType).yieldsAsync();
updateHandler.update(function(error) {
expect(error).to.not.exist;
expect(userStorageStub.removeList.calledOnce).to.be.true;
expect(appConfigStorageStub.storeList.calledOnce).to.be.true;
done();
});
});
it('should fail when persisting database version fails', function(done) {
userStorageStub.removeList.yieldsAsync();
appConfigStorageStub.storeList.yieldsAsync(new Error());
updateHandler.update(function(error) {
expect(error).to.exist;
expect(userStorageStub.removeList.calledOnce).to.be.true;
expect(appConfigStorageStub.storeList.calledOnce).to.be.true;
done();
});
});
it('should fail when wiping emails from database fails', function(done) {
userStorageStub.removeList.yieldsAsync(new Error());
updateHandler.update(function(error) {
expect(error).to.exist;
expect(userStorageStub.removeList.calledOnce).to.be.true;
expect(appConfigStorageStub.storeList.called).to.be.false;
done();
});
done();
});
});
describe('v1 -> v2', function() {
var emailDbType = 'email_';
});
beforeEach(function() {
cfg.dbVersion = 2; // app requires database version 2
appConfigStorageStub.listItems.withArgs(versionDbType).yieldsAsync(null, [1]); // database version is 0
});
describe('v0 -> v1', function() {
var emailDbType = 'email_';
afterEach(function() {
// database version is only queried for version checking prior to the update script
// so no need to check this in case-specific tests
expect(appConfigStorageStub.listItems.calledOnce).to.be.true;
});
beforeEach(function() {
cfg.dbVersion = 1; // app requires database version 1
appConfigStorageStub.listItems.withArgs(versionDbType).yieldsAsync(); // database version is 0
});
it('should work', function(done) {
userStorageStub.removeList.withArgs(emailDbType).yieldsAsync();
appConfigStorageStub.storeList.withArgs([2], versionDbType).yieldsAsync();
afterEach(function() {
// database version is only queried for version checking prior to the update script
// so no need to check this in case-specific tests
expect(appConfigStorageStub.listItems.calledOnce).to.be.true;
});
updateHandler.update(function(error) {
expect(error).to.not.exist;
expect(userStorageStub.removeList.calledOnce).to.be.true;
expect(appConfigStorageStub.storeList.calledOnce).to.be.true;
it('should work', function(done) {
userStorageStub.removeList.withArgs(emailDbType).yieldsAsync();
appConfigStorageStub.storeList.withArgs([1], versionDbType).yieldsAsync();
done();
});
});
updateHandler.update(function(error) {
expect(error).to.not.exist;
expect(userStorageStub.removeList.calledOnce).to.be.true;
expect(appConfigStorageStub.storeList.calledOnce).to.be.true;
it('should fail when persisting database version fails', function(done) {
userStorageStub.removeList.yieldsAsync();
appConfigStorageStub.storeList.yieldsAsync(new Error());
updateHandler.update(function(error) {
expect(error).to.exist;
expect(userStorageStub.removeList.calledOnce).to.be.true;
expect(appConfigStorageStub.storeList.calledOnce).to.be.true;
done();
});
});
it('should fail when wiping emails from database fails', function(done) {
userStorageStub.removeList.yieldsAsync(new Error());
updateHandler.update(function(error) {
expect(error).to.exist;
expect(userStorageStub.removeList.calledOnce).to.be.true;
expect(appConfigStorageStub.storeList.called).to.be.false;
done();
});
done();
});
});
describe('v2 -> v3', function() {
var emailDbType = 'email_';
it('should fail when persisting database version fails', function(done) {
userStorageStub.removeList.yieldsAsync();
appConfigStorageStub.storeList.yieldsAsync(new Error());
beforeEach(function() {
cfg.dbVersion = 3; // app requires database version 2
appConfigStorageStub.listItems.withArgs(versionDbType).yieldsAsync(null, [2]); // database version is 0
});
updateHandler.update(function(error) {
expect(error).to.exist;
expect(userStorageStub.removeList.calledOnce).to.be.true;
expect(appConfigStorageStub.storeList.calledOnce).to.be.true;
afterEach(function() {
// database version is only queried for version checking prior to the update script
// so no need to check this in case-specific tests
expect(appConfigStorageStub.listItems.calledOnce).to.be.true;
});
it('should work', function(done) {
userStorageStub.removeList.withArgs(emailDbType).yieldsAsync();
appConfigStorageStub.storeList.withArgs([3], versionDbType).yieldsAsync();
updateHandler.update(function(error) {
expect(error).to.not.exist;
expect(userStorageStub.removeList.calledOnce).to.be.true;
expect(appConfigStorageStub.storeList.calledOnce).to.be.true;
done();
});
});
it('should fail when persisting database version fails', function(done) {
userStorageStub.removeList.yieldsAsync();
appConfigStorageStub.storeList.yieldsAsync(new Error());
updateHandler.update(function(error) {
expect(error).to.exist;
expect(userStorageStub.removeList.calledOnce).to.be.true;
expect(appConfigStorageStub.storeList.calledOnce).to.be.true;
done();
});
});
it('should fail when wiping emails from database fails', function(done) {
userStorageStub.removeList.yieldsAsync(new Error());
updateHandler.update(function(error) {
expect(error).to.exist;
expect(userStorageStub.removeList.calledOnce).to.be.true;
expect(appConfigStorageStub.storeList.called).to.be.false;
done();
});
});
});
describe('v3 -> v4', function() {
var EMAIL_ADDR_DB_KEY = 'emailaddress';
var USERNAME_DB_KEY = 'username';
var PROVIDER_DB_KEY = 'provider';
var IMAP_DB_KEY = 'imap';
var SMTP_DB_KEY = 'smtp';
var REALNAME_DB_KEY = 'realname';
var emailaddress = 'bla@blubb.io';
var imap = config.gmail.imap,
smtp = config.gmail.smtp;
beforeEach(function() {
cfg.dbVersion = 4; // app requires database version 4
appConfigStorageStub.listItems.withArgs(versionDbType).yieldsAsync(null, [3]); // database version is 3
});
it('should add gmail as mail service provider with email address and no provider present in db', function(done) {
appConfigStorageStub.listItems.withArgs(EMAIL_ADDR_DB_KEY).yieldsAsync(null, [emailaddress]);
appConfigStorageStub.listItems.withArgs(PROVIDER_DB_KEY).yieldsAsync(null, []);
appConfigStorageStub.storeList.withArgs([4], versionDbType).yieldsAsync();
appConfigStorageStub.storeList.withArgs(['gmail'], PROVIDER_DB_KEY).yieldsAsync();
appConfigStorageStub.storeList.withArgs([emailaddress], USERNAME_DB_KEY).yieldsAsync();
appConfigStorageStub.storeList.withArgs([imap], IMAP_DB_KEY).yieldsAsync();
appConfigStorageStub.storeList.withArgs([smtp], SMTP_DB_KEY).yieldsAsync();
appConfigStorageStub.storeList.withArgs([''], REALNAME_DB_KEY).yieldsAsync();
authStub._loadCredentials.yieldsAsync();
updateHandler.update(function(error) {
expect(error).to.not.exist;
expect(appConfigStorageStub.storeList.callCount).to.equal(6);
expect(appConfigStorageStub.listItems.calledThrice).to.be.true;
expect(authStub._loadCredentials.calledOnce).to.be.true;
done();
});
});
it('should not add a provider when no email adress is in db', function(done) {
appConfigStorageStub.listItems.withArgs(EMAIL_ADDR_DB_KEY).yieldsAsync(null, []);
appConfigStorageStub.listItems.withArgs(PROVIDER_DB_KEY).yieldsAsync(null, []);
appConfigStorageStub.storeList.withArgs([4], versionDbType).yieldsAsync();
updateHandler.update(function(error) {
expect(error).to.not.exist;
expect(appConfigStorageStub.storeList.calledOnce).to.be.true;
expect(appConfigStorageStub.listItems.calledThrice).to.be.true;
done();
});
});
it('should fail when appConfigStore write fails', function(done) {
appConfigStorageStub.listItems.yieldsAsync(null, []);
appConfigStorageStub.storeList.yieldsAsync(new Error());
updateHandler.update(function(error) {
expect(error).to.exist;
expect(appConfigStorageStub.listItems.calledThrice).to.be.true;
expect(appConfigStorageStub.storeList.calledOnce).to.be.true;
done();
});
});
it('should fail when appConfigStore read fails', function(done) {
appConfigStorageStub.listItems.withArgs(EMAIL_ADDR_DB_KEY).yieldsAsync(new Error());
appConfigStorageStub.storeList.yieldsAsync(new Error());
updateHandler.update(function(error) {
expect(error).to.exist;
expect(appConfigStorageStub.listItems.calledTwice).to.be.true;
expect(appConfigStorageStub.storeList.called).to.be.false;
done();
});
done();
});
});
describe('v4 -> v5', function() {
var FOLDER_TYPE_INBOX = 'Inbox';
var FOLDER_TYPE_SENT = 'Sent';
var FOLDER_TYPE_DRAFTS = 'Drafts';
var FOLDER_TYPE_TRASH = 'Trash';
it('should fail when wiping emails from database fails', function(done) {
userStorageStub.removeList.yieldsAsync(new Error());
var FOLDER_DB_TYPE = 'folders';
var VERSION_DB_TYPE = 'dbVersion';
updateHandler.update(function(error) {
expect(error).to.exist;
expect(userStorageStub.removeList.calledOnce).to.be.true;
expect(appConfigStorageStub.storeList.called).to.be.false;
var POST_UPDATE_DB_VERSION = 5;
beforeEach(function() {
cfg.dbVersion = 5; // app requires database version 4
appConfigStorageStub.listItems.withArgs(VERSION_DB_TYPE).yieldsAsync(null, [4]); // database version is 4
done();
});
});
});
afterEach(function() {
// database version is only queried for version checking prior to the update script
// so no need to check this in case-specific tests
expect(appConfigStorageStub.listItems.calledOnce).to.be.true;
describe('v1 -> v2', function() {
var emailDbType = 'email_';
beforeEach(function() {
cfg.dbVersion = 2; // app requires database version 2
appConfigStorageStub.listItems.withArgs(versionDbType).yieldsAsync(null, [1]); // database version is 0
});
afterEach(function() {
// database version is only queried for version checking prior to the update script
// so no need to check this in case-specific tests
expect(appConfigStorageStub.listItems.calledOnce).to.be.true;
});
it('should work', function(done) {
userStorageStub.removeList.withArgs(emailDbType).yieldsAsync();
appConfigStorageStub.storeList.withArgs([2], versionDbType).yieldsAsync();
updateHandler.update(function(error) {
expect(error).to.not.exist;
expect(userStorageStub.removeList.calledOnce).to.be.true;
expect(appConfigStorageStub.storeList.calledOnce).to.be.true;
done();
});
});
it('should work', function(done) {
userStorageStub.listItems.withArgs(FOLDER_DB_TYPE).yieldsAsync(null, [
[{
name: 'inbox1',
type: FOLDER_TYPE_INBOX
}, {
name: 'inbox2',
type: FOLDER_TYPE_INBOX
}, {
name: 'sent1',
type: FOLDER_TYPE_SENT
}, {
name: 'sent2',
type: FOLDER_TYPE_SENT
}, {
name: 'drafts1',
type: FOLDER_TYPE_DRAFTS
}, {
name: 'drafts2',
type: FOLDER_TYPE_DRAFTS
}, {
name: 'trash1',
type: FOLDER_TYPE_TRASH
}, {
name: 'trash2',
type: FOLDER_TYPE_TRASH
}]
]);
it('should fail when persisting database version fails', function(done) {
userStorageStub.removeList.yieldsAsync();
appConfigStorageStub.storeList.yieldsAsync(new Error());
userStorageStub.storeList.withArgs([
[{
name: 'inbox1',
type: FOLDER_TYPE_INBOX
}, {
name: 'sent1',
type: FOLDER_TYPE_SENT
}, {
name: 'drafts1',
type: FOLDER_TYPE_DRAFTS
}, {
name: 'trash1',
type: FOLDER_TYPE_TRASH
}]
], FOLDER_DB_TYPE).yieldsAsync();
updateHandler.update(function(error) {
expect(error).to.exist;
expect(userStorageStub.removeList.calledOnce).to.be.true;
expect(appConfigStorageStub.storeList.calledOnce).to.be.true;
appConfigStorageStub.storeList.withArgs([POST_UPDATE_DB_VERSION], VERSION_DB_TYPE).yieldsAsync();
updateHandler.update(function(error) {
expect(error).to.not.exist;
expect(userStorageStub.listItems.calledOnce).to.be.true;
expect(userStorageStub.storeList.calledOnce).to.be.true;
expect(appConfigStorageStub.storeList.calledOnce).to.be.true;
done();
});
done();
});
});
it('should fail when persisting database version fails', function(done) {
userStorageStub.listItems.yieldsAsync(null, []);
userStorageStub.storeList.yieldsAsync();
appConfigStorageStub.storeList.yieldsAsync(new Error());
it('should fail when wiping emails from database fails', function(done) {
userStorageStub.removeList.yieldsAsync(new Error());
updateHandler.update(function(error) {
expect(error).to.exist;
expect(userStorageStub.listItems.calledOnce).to.be.true;
expect(userStorageStub.storeList.calledOnce).to.be.true;
expect(appConfigStorageStub.storeList.calledOnce).to.be.true;
updateHandler.update(function(error) {
expect(error).to.exist;
expect(userStorageStub.removeList.calledOnce).to.be.true;
expect(appConfigStorageStub.storeList.called).to.be.false;
done();
});
done();
});
});
});
it('should fail when persisting folders fails', function(done) {
userStorageStub.listItems.yieldsAsync(null, []);
userStorageStub.storeList.yieldsAsync(new Error());
describe('v2 -> v3', function() {
var emailDbType = 'email_';
updateHandler.update(function(error) {
expect(error).to.exist;
expect(userStorageStub.listItems.calledOnce).to.be.true;
expect(userStorageStub.storeList.calledOnce).to.be.true;
expect(appConfigStorageStub.storeList.called).to.be.false;
beforeEach(function() {
cfg.dbVersion = 3; // app requires database version 2
appConfigStorageStub.listItems.withArgs(versionDbType).yieldsAsync(null, [2]); // database version is 0
});
done();
});
afterEach(function() {
// database version is only queried for version checking prior to the update script
// so no need to check this in case-specific tests
expect(appConfigStorageStub.listItems.calledOnce).to.be.true;
});
it('should work', function(done) {
userStorageStub.removeList.withArgs(emailDbType).yieldsAsync();
appConfigStorageStub.storeList.withArgs([3], versionDbType).yieldsAsync();
updateHandler.update(function(error) {
expect(error).to.not.exist;
expect(userStorageStub.removeList.calledOnce).to.be.true;
expect(appConfigStorageStub.storeList.calledOnce).to.be.true;
done();
});
});
it('should fail when listing folders fails', function(done) {
userStorageStub.listItems.yieldsAsync(new Error());
it('should fail when persisting database version fails', function(done) {
userStorageStub.removeList.yieldsAsync();
appConfigStorageStub.storeList.yieldsAsync(new Error());
updateHandler.update(function(error) {
expect(error).to.exist;
expect(userStorageStub.listItems.calledOnce).to.be.true;
expect(userStorageStub.storeList.called).to.be.false;
expect(appConfigStorageStub.storeList.called).to.be.false;
updateHandler.update(function(error) {
expect(error).to.exist;
expect(userStorageStub.removeList.calledOnce).to.be.true;
expect(appConfigStorageStub.storeList.calledOnce).to.be.true;
done();
});
done();
});
});
it('should fail when wiping emails from database fails', function(done) {
userStorageStub.removeList.yieldsAsync(new Error());
updateHandler.update(function(error) {
expect(error).to.exist;
expect(userStorageStub.removeList.calledOnce).to.be.true;
expect(appConfigStorageStub.storeList.called).to.be.false;
done();
});
});
});
describe('v3 -> v4', function() {
var EMAIL_ADDR_DB_KEY = 'emailaddress';
var USERNAME_DB_KEY = 'username';
var PROVIDER_DB_KEY = 'provider';
var IMAP_DB_KEY = 'imap';
var SMTP_DB_KEY = 'smtp';
var REALNAME_DB_KEY = 'realname';
var emailaddress = 'bla@blubb.io';
var imap = config.gmail.imap,
smtp = config.gmail.smtp;
beforeEach(function() {
cfg.dbVersion = 4; // app requires database version 4
appConfigStorageStub.listItems.withArgs(versionDbType).yieldsAsync(null, [3]); // database version is 3
});
it('should add gmail as mail service provider with email address and no provider present in db', function(done) {
appConfigStorageStub.listItems.withArgs(EMAIL_ADDR_DB_KEY).yieldsAsync(null, [emailaddress]);
appConfigStorageStub.listItems.withArgs(PROVIDER_DB_KEY).yieldsAsync(null, []);
appConfigStorageStub.storeList.withArgs([4], versionDbType).yieldsAsync();
appConfigStorageStub.storeList.withArgs(['gmail'], PROVIDER_DB_KEY).yieldsAsync();
appConfigStorageStub.storeList.withArgs([emailaddress], USERNAME_DB_KEY).yieldsAsync();
appConfigStorageStub.storeList.withArgs([imap], IMAP_DB_KEY).yieldsAsync();
appConfigStorageStub.storeList.withArgs([smtp], SMTP_DB_KEY).yieldsAsync();
appConfigStorageStub.storeList.withArgs([''], REALNAME_DB_KEY).yieldsAsync();
authStub._loadCredentials.yieldsAsync();
updateHandler.update(function(error) {
expect(error).to.not.exist;
expect(appConfigStorageStub.storeList.callCount).to.equal(6);
expect(appConfigStorageStub.listItems.calledThrice).to.be.true;
expect(authStub._loadCredentials.calledOnce).to.be.true;
done();
});
});
it('should not add a provider when no email adress is in db', function(done) {
appConfigStorageStub.listItems.withArgs(EMAIL_ADDR_DB_KEY).yieldsAsync(null, []);
appConfigStorageStub.listItems.withArgs(PROVIDER_DB_KEY).yieldsAsync(null, []);
appConfigStorageStub.storeList.withArgs([4], versionDbType).yieldsAsync();
updateHandler.update(function(error) {
expect(error).to.not.exist;
expect(appConfigStorageStub.storeList.calledOnce).to.be.true;
expect(appConfigStorageStub.listItems.calledThrice).to.be.true;
done();
});
});
it('should fail when appConfigStore write fails', function(done) {
appConfigStorageStub.listItems.yieldsAsync(null, []);
appConfigStorageStub.storeList.yieldsAsync(new Error());
updateHandler.update(function(error) {
expect(error).to.exist;
expect(appConfigStorageStub.listItems.calledThrice).to.be.true;
expect(appConfigStorageStub.storeList.calledOnce).to.be.true;
done();
});
});
it('should fail when appConfigStore read fails', function(done) {
appConfigStorageStub.listItems.withArgs(EMAIL_ADDR_DB_KEY).yieldsAsync(new Error());
appConfigStorageStub.storeList.yieldsAsync(new Error());
updateHandler.update(function(error) {
expect(error).to.exist;
expect(appConfigStorageStub.listItems.calledTwice).to.be.true;
expect(appConfigStorageStub.storeList.called).to.be.false;
done();
});
});
});
describe('v4 -> v5', function() {
var FOLDER_TYPE_INBOX = 'Inbox';
var FOLDER_TYPE_SENT = 'Sent';
var FOLDER_TYPE_DRAFTS = 'Drafts';
var FOLDER_TYPE_TRASH = 'Trash';
var FOLDER_DB_TYPE = 'folders';
var VERSION_DB_TYPE = 'dbVersion';
var POST_UPDATE_DB_VERSION = 5;
beforeEach(function() {
cfg.dbVersion = 5; // app requires database version 4
appConfigStorageStub.listItems.withArgs(VERSION_DB_TYPE).yieldsAsync(null, [4]); // database version is 4
});
afterEach(function() {
// database version is only queried for version checking prior to the update script
// so no need to check this in case-specific tests
expect(appConfigStorageStub.listItems.calledOnce).to.be.true;
});
it('should work', function(done) {
userStorageStub.listItems.withArgs(FOLDER_DB_TYPE).yieldsAsync(null, [
[{
name: 'inbox1',
type: FOLDER_TYPE_INBOX
}, {
name: 'inbox2',
type: FOLDER_TYPE_INBOX
}, {
name: 'sent1',
type: FOLDER_TYPE_SENT
}, {
name: 'sent2',
type: FOLDER_TYPE_SENT
}, {
name: 'drafts1',
type: FOLDER_TYPE_DRAFTS
}, {
name: 'drafts2',
type: FOLDER_TYPE_DRAFTS
}, {
name: 'trash1',
type: FOLDER_TYPE_TRASH
}, {
name: 'trash2',
type: FOLDER_TYPE_TRASH
}]
]);
userStorageStub.storeList.withArgs([
[{
name: 'inbox1',
type: FOLDER_TYPE_INBOX
}, {
name: 'sent1',
type: FOLDER_TYPE_SENT
}, {
name: 'drafts1',
type: FOLDER_TYPE_DRAFTS
}, {
name: 'trash1',
type: FOLDER_TYPE_TRASH
}]
], FOLDER_DB_TYPE).yieldsAsync();
appConfigStorageStub.storeList.withArgs([POST_UPDATE_DB_VERSION], VERSION_DB_TYPE).yieldsAsync();
updateHandler.update(function(error) {
expect(error).to.not.exist;
expect(userStorageStub.listItems.calledOnce).to.be.true;
expect(userStorageStub.storeList.calledOnce).to.be.true;
expect(appConfigStorageStub.storeList.calledOnce).to.be.true;
done();
});
});
it('should fail when persisting database version fails', function(done) {
userStorageStub.listItems.yieldsAsync(null, []);
userStorageStub.storeList.yieldsAsync();
appConfigStorageStub.storeList.yieldsAsync(new Error());
updateHandler.update(function(error) {
expect(error).to.exist;
expect(userStorageStub.listItems.calledOnce).to.be.true;
expect(userStorageStub.storeList.calledOnce).to.be.true;
expect(appConfigStorageStub.storeList.calledOnce).to.be.true;
done();
});
});
it('should fail when persisting folders fails', function(done) {
userStorageStub.listItems.yieldsAsync(null, []);
userStorageStub.storeList.yieldsAsync(new Error());
updateHandler.update(function(error) {
expect(error).to.exist;
expect(userStorageStub.listItems.calledOnce).to.be.true;
expect(userStorageStub.storeList.calledOnce).to.be.true;
expect(appConfigStorageStub.storeList.called).to.be.false;
done();
});
});
it('should fail when listing folders fails', function(done) {
userStorageStub.listItems.yieldsAsync(new Error());
updateHandler.update(function(error) {
expect(error).to.exist;
expect(userStorageStub.listItems.calledOnce).to.be.true;
expect(userStorageStub.storeList.called).to.be.false;
expect(appConfigStorageStub.storeList.called).to.be.false;
done();
});
});
});

View File

@ -1,401 +1,397 @@
define(function(require) {
'use strict';
'use strict';
var expect = chai.expect,
angular = require('angular'),
mocks = require('angularMocks'),
WriteCtrl = require('js/controller/write'),
EmailDAO = require('js/dao/email-dao'),
OutboxBO = require('js/bo/outbox'),
KeychainDAO = require('js/dao/keychain-dao'),
appController = require('js/app-controller');
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');
describe('Write controller unit test', function() {
var ctrl, scope,
origEmailDao, origOutbox, origKeychain,
emailDaoMock, keychainMock, outboxMock, emailAddress, realname;
describe('Write controller unit test', function() {
var ctrl, scope,
origEmailDao, origOutbox, origKeychain,
emailDaoMock, 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;
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;
outboxMock = sinon.createStubInstance(OutboxBO);
appController._outboxBo = outboxMock;
emailDaoMock = sinon.createStubInstance(EmailDAO);
appController._emailDao = emailDaoMock;
emailDaoMock = sinon.createStubInstance(EmailDAO);
appController._emailDao = emailDaoMock;
emailAddress = 'fred@foo.com';
realname = 'Fred Foo';
emailDaoMock._account = {
emailAddress: emailAddress,
realname: realname
};
emailAddress = 'fred@foo.com';
realname = 'Fred Foo';
emailDaoMock._account = {
emailAddress: emailAddress,
realname: realname
};
keychainMock = sinon.createStubInstance(KeychainDAO);
appController._keychain = keychainMock;
keychainMock = sinon.createStubInstance(KeychainDAO);
appController._keychain = keychainMock;
angular.module('writetest', []);
mocks.module('writetest');
mocks.inject(function($rootScope, $controller) {
scope = $rootScope.$new();
scope.state = {};
ctrl = $controller(WriteCtrl, {
$scope: scope
});
angular.module('writetest', []);
mocks.module('writetest');
mocks.inject(function($rootScope, $controller) {
scope = $rootScope.$new();
scope.state = {};
ctrl = $controller(WriteCtrl, {
$scope: scope
});
});
});
afterEach(function() {
// restore the app controller
appController._emailDao = origEmailDao;
appController._outboxBo = origOutbox;
appController._keychain = origKeychain;
afterEach(function() {
// restore the app controller
appController._emailDao = origEmailDao;
appController._outboxBo = origOutbox;
appController._keychain = origKeychain;
});
describe('scope variables', function() {
it('should be set correctly', function() {
expect(scope.state.writer).to.exist;
expect(scope.state.lightbox).to.be.undefined;
expect(scope.state.writer.write).to.exist;
expect(scope.state.writer.close).to.exist;
expect(scope.verify).to.exist;
expect(scope.checkSendStatus).to.exist;
expect(scope.sendToOutbox).to.exist;
expect(scope.tagStyle).to.exist;
expect(scope.lookupAddressBook).to.exist;
});
});
describe('close', function() {
it('should close the writer', function() {
scope.state.lightbox = 'write';
scope.state.writer.close();
expect(scope.state.lightbox).to.be.undefined;
});
});
describe('write', function() {
it('should prepare write view', function() {
var verifyMock = sinon.stub(scope, 'verify');
scope.state.writer.write();
expect(scope.writerTitle).to.equal('New email');
expect(scope.to).to.deep.equal([]);
expect(scope.subject).to.equal('');
expect(scope.body).to.equal('');
expect(verifyMock.calledOnce).to.be.true;
scope.verify.restore();
});
describe('scope variables', function() {
it('should be set correctly', function() {
expect(scope.state.writer).to.exist;
expect(scope.state.lightbox).to.be.undefined;
expect(scope.state.writer.write).to.exist;
expect(scope.state.writer.close).to.exist;
expect(scope.verify).to.exist;
expect(scope.checkSendStatus).to.exist;
expect(scope.sendToOutbox).to.exist;
expect(scope.tagStyle).to.exist;
expect(scope.lookupAddressBook).to.exist;
});
it('should prefill write view for response', function() {
var verifyMock = sinon.stub(scope, 'verify'),
address = 'pity@dafool',
subject = 'Ermahgerd!',
body = 'so much body!',
re = {
id: 'abc',
from: [{
address: address
}],
subject: subject,
sentDate: new Date(),
body: body,
references: ['ghi', 'def']
};
scope.sendBtnSecure = true;
scope.state.writer.write(re);
expect(scope.writerTitle).to.equal('Reply');
expect(scope.to).to.deep.equal([{
address: address,
}]);
expect(scope.subject).to.equal('Re: ' + subject);
expect(scope.body).to.contain(body);
expect(scope.references).to.deep.equal(['ghi', 'def', 'abc']);
expect(verifyMock.called).to.be.true;
scope.verify.restore();
});
describe('close', function() {
it('should close the writer', function() {
scope.state.lightbox = 'write';
scope.state.writer.close();
expect(scope.state.lightbox).to.be.undefined;
});
});
describe('write', function() {
it('should prepare write view', function() {
var verifyMock = sinon.stub(scope, 'verify');
scope.state.writer.write();
expect(scope.writerTitle).to.equal('New email');
expect(scope.to).to.deep.equal([]);
expect(scope.subject).to.equal('');
expect(scope.body).to.equal('');
expect(verifyMock.calledOnce).to.be.true;
scope.verify.restore();
});
it('should prefill write view for response', function() {
var verifyMock = sinon.stub(scope, 'verify'),
address = 'pity@dafool',
subject = 'Ermahgerd!',
body = 'so much body!',
re = {
id: 'abc',
from: [{
address: address
}],
subject: subject,
sentDate: new Date(),
body: body,
references: ['ghi', 'def']
};
scope.sendBtnSecure = true;
scope.state.writer.write(re);
expect(scope.writerTitle).to.equal('Reply');
expect(scope.to).to.deep.equal([{
address: address,
}]);
expect(scope.subject).to.equal('Re: ' + subject);
expect(scope.body).to.contain(body);
expect(scope.references).to.deep.equal(['ghi', 'def', 'abc']);
expect(verifyMock.called).to.be.true;
scope.verify.restore();
});
it('should prefill write view for forward', function() {
var verifyMock = sinon.stub(scope, 'verify'),
address = 'pity@dafool',
subject = 'Ermahgerd!',
body = 'so much body!',
re = {
from: [{
address: address
}],
to: [{
address: address
}],
subject: subject,
sentDate: new Date(),
body: body,
attachments: [{}]
};
scope.sendBtnSecure = false;
scope.state.writer.write(re, null, true);
expect(scope.writerTitle).to.equal('Forward');
expect(scope.to).to.deep.equal([]);
expect(scope.subject).to.equal('Fwd: ' + subject);
expect(scope.body).to.contain(body);
expect(verifyMock.called).to.be.true;
expect(scope.attachments).to.not.equal(re.attachments); // not the same reference
expect(scope.attachments).to.deep.equal(re.attachments); // but the same content
scope.verify.restore();
});
});
describe('verify', function() {
var checkSendStatusMock;
beforeEach(function() {
checkSendStatusMock = sinon.stub(scope, 'checkSendStatus');
});
afterEach(function() {
scope.checkSendStatus.restore();
});
it('should do nothing if recipient is not provided', function() {
scope.verify(undefined);
});
it('should not work for invalid email addresses', function() {
var recipient = {
address: ''
it('should prefill write view for forward', function() {
var verifyMock = sinon.stub(scope, 'verify'),
address = 'pity@dafool',
subject = 'Ermahgerd!',
body = 'so much body!',
re = {
from: [{
address: address
}],
to: [{
address: address
}],
subject: subject,
sentDate: new Date(),
body: body,
attachments: [{}]
};
scope.verify(recipient);
scope.sendBtnSecure = false;
expect(recipient.key).to.be.undefined;
expect(recipient.secure).to.be.undefined;
expect(scope.checkSendStatus.callCount).to.equal(2);
expect(keychainMock.getReceiverPublicKey.called).to.be.false;
});
scope.state.writer.write(re, null, true);
it('should not work for error in keychain', function(done) {
var recipient = {
address: 'asds@example.com'
};
expect(scope.writerTitle).to.equal('Forward');
expect(scope.to).to.deep.equal([]);
expect(scope.subject).to.equal('Fwd: ' + subject);
expect(scope.body).to.contain(body);
expect(verifyMock.called).to.be.true;
expect(scope.attachments).to.not.equal(re.attachments); // not the same reference
expect(scope.attachments).to.deep.equal(re.attachments); // but the same content
keychainMock.refreshKeyForUserId.withArgs(recipient.address).yields({
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);
});
it('should work for main userId', function(done) {
var recipient = {
address: 'asdf@example.com'
};
keychainMock.refreshKeyForUserId.withArgs(recipient.address).yields(null, {
userId: 'asdf@example.com'
});
scope.$digest = function() {
expect(recipient.key).to.deep.equal({
userId: 'asdf@example.com'
});
expect(recipient.secure).to.be.true;
expect(scope.checkSendStatus.callCount).to.equal(2);
expect(keychainMock.refreshKeyForUserId.calledOnce).to.be.true;
done();
};
scope.verify(recipient);
});
it('should work for secondary userId', function(done) {
var recipient = {
address: 'asdf@example.com'
};
var key = {
userId: 'qwer@example.com',
userIds: [{
emailAddress: 'asdf@example.com'
}]
};
keychainMock.refreshKeyForUserId.withArgs(recipient.address).yields(null, key);
scope.$digest = function() {
expect(recipient.key).to.deep.equal(key);
expect(recipient.secure).to.be.true;
expect(scope.checkSendStatus.callCount).to.equal(2);
expect(keychainMock.refreshKeyForUserId.calledOnce).to.be.true;
done();
};
scope.verify(recipient);
});
});
describe('checkSendStatus', function() {
beforeEach(function() {
scope.state.writer.write();
});
afterEach(function() {});
it('should not be able to send with no recipients', function() {
scope.checkSendStatus();
expect(scope.okToSend).to.be.false;
expect(scope.sendBtnText).to.be.undefined;
expect(scope.sendBtnSecure).to.be.undefined;
});
it('should be able to send plaintext', function() {
scope.to = [{
address: 'asdf@asdf.de'
}];
scope.checkSendStatus();
expect(scope.okToSend).to.be.true;
expect(scope.sendBtnText).to.equal('Send');
expect(scope.sendBtnSecure).to.be.false;
});
it('should send plaintext if one receiver is not secure', function() {
scope.to = [{
address: 'asdf@asdf.de',
secure: true
}, {
address: 'asdf@asdfg.de'
}];
scope.checkSendStatus();
expect(scope.okToSend).to.be.true;
expect(scope.sendBtnText).to.equal('Send');
expect(scope.sendBtnSecure).to.be.false;
});
it('should be able to send securely to multiple recipients', function() {
scope.to = [{
address: 'asdf@asdf.de',
secure: true
}, {
address: 'asdf@asdfg.de',
secure: true
}];
scope.checkSendStatus();
expect(scope.okToSend).to.be.true;
expect(scope.sendBtnText).to.equal('Send securely');
expect(scope.sendBtnSecure).to.be.true;
});
});
describe('send to outbox', function() {
it('should work', function() {
scope.to = [{
address: 'pity@dafool'
}];
scope.cc = [];
scope.bcc = [];
scope.subject = 'Ermahgerd!';
scope.body = 'wow. much body! very text!';
scope.attachments = [];
scope.state.nav = {
currentFolder: 'currentFolder'
};
scope.replyTo = {};
outboxMock.put.withArgs(sinon.match(function(mail) {
expect(mail.from).to.deep.equal([{
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);
expect(mail.body).to.contain(scope.body);
expect(mail.subject).to.equal(scope.subject);
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;
};
scope.sendToOutbox();
expect(outboxMock.put.calledOnce).to.be.true;
expect(emailDaoMock.setFlags.calledOnce).to.be.true;
expect(scope.state.lightbox).to.be.undefined;
expect(scope.replyTo.answered).to.be.true;
});
});
describe('lookupAddressBook', function() {
it('should work', function(done) {
keychainMock.listLocalPublicKeys.yields(null, [{
userId: 'test@asdf.com',
publicKey: 'KEY'
}]);
var result = scope.lookupAddressBook('test');
result.then(function(response) {
expect(response).to.deep.equal([{
address: 'test@asdf.com'
}]);
done();
});
scope.$digest();
});
it('should work with cache', function(done) {
scope.addressBookCache = [{
address: 'test@asdf.com'
}, {
address: 'tes@asdf.com'
}];
var result = scope.lookupAddressBook('test');
result.then(function(response) {
expect(response).to.deep.equal([{
address: 'test@asdf.com'
}]);
done();
});
scope.$digest();
});
scope.verify.restore();
});
});
describe('verify', function() {
var checkSendStatusMock;
beforeEach(function() {
checkSendStatusMock = sinon.stub(scope, 'checkSendStatus');
});
afterEach(function() {
scope.checkSendStatus.restore();
});
it('should do nothing if recipient is not provided', function() {
scope.verify(undefined);
});
it('should not work for invalid email addresses', function() {
var recipient = {
address: ''
};
scope.verify(recipient);
expect(recipient.key).to.be.undefined;
expect(recipient.secure).to.be.undefined;
expect(scope.checkSendStatus.callCount).to.equal(2);
expect(keychainMock.getReceiverPublicKey.called).to.be.false;
});
it('should not work for error in keychain', function(done) {
var recipient = {
address: 'asds@example.com'
};
keychainMock.refreshKeyForUserId.withArgs(recipient.address).yields({
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);
});
it('should work for main userId', function(done) {
var recipient = {
address: 'asdf@example.com'
};
keychainMock.refreshKeyForUserId.withArgs(recipient.address).yields(null, {
userId: 'asdf@example.com'
});
scope.$digest = function() {
expect(recipient.key).to.deep.equal({
userId: 'asdf@example.com'
});
expect(recipient.secure).to.be.true;
expect(scope.checkSendStatus.callCount).to.equal(2);
expect(keychainMock.refreshKeyForUserId.calledOnce).to.be.true;
done();
};
scope.verify(recipient);
});
it('should work for secondary userId', function(done) {
var recipient = {
address: 'asdf@example.com'
};
var key = {
userId: 'qwer@example.com',
userIds: [{
emailAddress: 'asdf@example.com'
}]
};
keychainMock.refreshKeyForUserId.withArgs(recipient.address).yields(null, key);
scope.$digest = function() {
expect(recipient.key).to.deep.equal(key);
expect(recipient.secure).to.be.true;
expect(scope.checkSendStatus.callCount).to.equal(2);
expect(keychainMock.refreshKeyForUserId.calledOnce).to.be.true;
done();
};
scope.verify(recipient);
});
});
describe('checkSendStatus', function() {
beforeEach(function() {
scope.state.writer.write();
});
afterEach(function() {});
it('should not be able to send with no recipients', function() {
scope.checkSendStatus();
expect(scope.okToSend).to.be.false;
expect(scope.sendBtnText).to.be.undefined;
expect(scope.sendBtnSecure).to.be.undefined;
});
it('should be able to send plaintext', function() {
scope.to = [{
address: 'asdf@asdf.de'
}];
scope.checkSendStatus();
expect(scope.okToSend).to.be.true;
expect(scope.sendBtnText).to.equal('Send');
expect(scope.sendBtnSecure).to.be.false;
});
it('should send plaintext if one receiver is not secure', function() {
scope.to = [{
address: 'asdf@asdf.de',
secure: true
}, {
address: 'asdf@asdfg.de'
}];
scope.checkSendStatus();
expect(scope.okToSend).to.be.true;
expect(scope.sendBtnText).to.equal('Send');
expect(scope.sendBtnSecure).to.be.false;
});
it('should be able to send securely to multiple recipients', function() {
scope.to = [{
address: 'asdf@asdf.de',
secure: true
}, {
address: 'asdf@asdfg.de',
secure: true
}];
scope.checkSendStatus();
expect(scope.okToSend).to.be.true;
expect(scope.sendBtnText).to.equal('Send securely');
expect(scope.sendBtnSecure).to.be.true;
});
});
describe('send to outbox', function() {
it('should work', function() {
scope.to = [{
address: 'pity@dafool'
}];
scope.cc = [];
scope.bcc = [];
scope.subject = 'Ermahgerd!';
scope.body = 'wow. much body! very text!';
scope.attachments = [];
scope.state.nav = {
currentFolder: 'currentFolder'
};
scope.replyTo = {};
outboxMock.put.withArgs(sinon.match(function(mail) {
expect(mail.from).to.deep.equal([{
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);
expect(mail.body).to.contain(scope.body);
expect(mail.subject).to.equal(scope.subject);
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;
};
scope.sendToOutbox();
expect(outboxMock.put.calledOnce).to.be.true;
expect(emailDaoMock.setFlags.calledOnce).to.be.true;
expect(scope.state.lightbox).to.be.undefined;
expect(scope.replyTo.answered).to.be.true;
});
});
describe('lookupAddressBook', function() {
it('should work', function(done) {
keychainMock.listLocalPublicKeys.yields(null, [{
userId: 'test@asdf.com',
publicKey: 'KEY'
}]);
var result = scope.lookupAddressBook('test');
result.then(function(response) {
expect(response).to.deep.equal([{
address: 'test@asdf.com'
}]);
done();
});
scope.$digest();
});
it('should work with cache', function(done) {
scope.addressBookCache = [{
address: 'test@asdf.com'
}, {
address: 'tes@asdf.com'
}];
var result = scope.lookupAddressBook('test');
result.then(function(response) {
expect(response).to.deep.equal([{
address: 'test@asdf.com'
}]);
done();
});
scope.$digest();
});
});
});