diff --git a/README.md b/README.md index 712f1f1..00ff41a 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,12 @@ html5-mail ========== -HTML5 Mail App with Client-side Encryption \ No newline at end of file +HTML5 Mail App with Client-side Encryption + +## Getting started +Required packages: nodejs, npm + + npm install express + node server.js 8080 --dev + +browse to http://localhost:8080 \ No newline at end of file diff --git a/dev_server.js b/dev_server.js deleted file mode 100644 index 39ca4e7..0000000 --- a/dev_server.js +++ /dev/null @@ -1,32 +0,0 @@ -/** - * A simple development server for serving static files using node.js - * Required packages: nodejs, npm - * 1. npm install express - * 2. node dev_server.js 8080 - * 3. browse to http://localhost:8080 - */ - -var express = require('express'), - port, app; - -// set port -if (process.argv[2]) { - port = process.argv[2]; -} else { - port = 8080; -} - -// Server setup -app = express(); -app.configure(function(){ - app.use(express['static'](__dirname)); - app.use(function(req, res, next) { - res.set('Content-Security-Policy', "script-src 'self' 'unsafe-eval'; object-src 'none'; style-src 'self' 'unsafe-inline'"); - return next(); - }); - app.use(express['static'](__dirname + '/src')); -}); - -// start server -app.listen(port); -console.log(' > listening on http://localhost:' + port); \ No newline at end of file diff --git a/manifest.webapp b/manifest.webapp deleted file mode 100644 index 03933a5..0000000 --- a/manifest.webapp +++ /dev/null @@ -1,15 +0,0 @@ -{ - "name": "Whiteout", - "description": "HTML5 Mail with an encrypted message store both locally and in the cloud", - "launch_path": "/html5-mail/src/", - "icons": { - "128": "/html5-mail/src/css/images/key-128.png" - }, - "developer": { - "name": "Whiteout Networks GmbH", - "url": "http://whiteout.io" - }, - "appcache_path": "/html5-mail/src/manifest.appcache", - "csp": "script-src 'self' 'unsafe-eval'; object-src 'none'; style-src 'self' 'unsafe-inline'", - "default_locale": "en" -} \ No newline at end of file diff --git a/server.js b/server.js new file mode 100644 index 0000000..dc7b1e6 --- /dev/null +++ b/server.js @@ -0,0 +1,49 @@ +/** + * A simple server for serving static files using node.js + */ + +var express = require('express'), + fs = require('fs'), + port, app, prot, dev; + +// set port +if (process.argv[2]) { + port = process.argv[2]; +} else { + port = 8080; +} + +if (process.argv[3] === '--dev') { + // development server + dev = true; + prot = 'http'; + app = express(); +} else { + // production server + dev = false; + prot = 'https'; + app = express({ + ca: fs.readFileSync('./ssl/sub.class1.server.ca.pem'), + key: fs.readFileSync('./ssl/ssl.key'), + cert: fs.readFileSync('./ssl/ssl.crt') + }); +} + +// Server setup +app.configure(function() { + // active content security policy for production + if (!dev) { + app.use(function(req, res, next) { + var csp = "script-src 'self' 'unsafe-eval'; object-src 'none'; style-src 'self' 'unsafe-inline'"; + res.set('Content-Security-Policy', csp); + res.set('X-Content-Security-Policy', csp); + res.set('X-WebKit-CSP', csp); + return next(); + }); + } + app.use(express['static'](__dirname + '/src')); +}); + +// start server +app.listen(port); +console.log(' > listening on ' + prot + '://localhost:' + port); \ No newline at end of file diff --git a/install.html b/src/install.html similarity index 100% rename from install.html rename to src/install.html diff --git a/src/js/app-config.js b/src/js/app-config.js index 0f01fcf..d808919 100644 --- a/src/js/app-config.js +++ b/src/js/app-config.js @@ -1,23 +1,28 @@ -'use strict'; +var app; -/** - * Create the application namespace - */ -var app = { - model: {}, - view: {}, - dao: {}, - crypto:{}, - util: {} -}; +(function() { + 'use strict'; -/** - * Global app configurations - */ -app.config = { - cloudUrl: 'https://whiteout-io.appspot.com', - symKeySize: 128, - symIvSize: 104, - asymKeySize: 2048, - workerPath: 'js' -}; \ No newline at end of file + /** + * Create the application namespace + */ + app = { + model: {}, + view: {}, + dao: {}, + crypto: {}, + util: {} + }; + + /** + * Global app configurations + */ + app.config = { + cloudUrl: 'https://whiteout-io.appspot.com', + symKeySize: 128, + symIvSize: 104, + asymKeySize: 2048, + workerPath: 'js' + }; + +}()); \ No newline at end of file diff --git a/src/js/app-router.js b/src/js/app-router.js index 545ea2f..0936a00 100644 --- a/src/js/app-router.js +++ b/src/js/app-router.js @@ -1,79 +1,92 @@ -'use strict'; +(function() { + 'use strict'; -var AppRouter = Backbone.Router.extend({ + app.Router = Backbone.Router.extend({ - routes:{ - '': 'login', - 'compose': 'compose', - 'accounts/:userId/folders': 'folders', - 'accounts/:userId/folders/:folder': 'messagelist', - 'accounts/:userId/folders/:folder/read/:messageId': 'read' - }, + routes: { + '': 'login', + 'compose': 'compose', + 'accounts/:userId/folders': 'folders', + 'accounts/:userId/folders/:folder': 'messagelist', + 'accounts/:userId/folders/:folder/read/:messageId': 'read' + }, - initialize: function () { - }, - - login: function() { - // init email dao and dependencies - var util = new app.crypto.Util(window, null); - var jsonDao = new app.dao.LawnchairDAO(window); - var crypto = new app.crypto.Crypto(window, util); - var cloudstorage = new app.dao.CloudStorage(window, $); - var devicestorage = new app.dao.DeviceStorage(util, crypto, jsonDao, null); - this.emailDao = new app.dao.EmailDAO(_, crypto, devicestorage, cloudstorage); - - var loginView = new app.view.LoginView({dao: this.emailDao}); - this.changePage(loginView); - }, - - compose: function() { - var composeView = new app.view.ComposeView(); - this.changePage(composeView); - }, - - folders: function(userId) { - var folderListView = new app.view.FolderListView({account: userId}); - this.changePage(folderListView); - }, - - messagelist: function(userId, folder) { - var self = this; - var messageListView = new app.view.MessageListView({account: userId, folder: folder, dao: this.emailDao}); - self.changePage(messageListView); - messageListView.loadItems(); - }, - - read: function(userId, folder, messageId) { - var readView = new app.view.ReadView({ - folder: folder, - messageId: decodeURIComponent(messageId), - dao: this.emailDao - }); - this.changePage(readView); - readView.renderBody(); - }, + initialize: function() {}, - changePage: function (page) { - // render the page and append it to the DOM - var pageEl = $(page.el); - pageEl.attr('data-role', 'page'); - page.render(); - $('body').append(pageEl); - - // handle back click - pageEl.on('vmousedown', '#backBtn', function(e) { - e.preventDefault(); - window.history.back(); - }); - - // change page for link buttons on vmousedown instead of waiting on vmouseup - pageEl.on('vmousedown', 'a[data-role="button"]', function(e) { - e.preventDefault(); - var href = $(e.currentTarget).attr('href'); - window.location = href; - }); - - $.mobile.changePage(pageEl, {changeHash:false, reverse:false}); - } + login: function() { + // init email dao and dependencies + var util = new app.crypto.Util(window, null); + var jsonDao = new app.dao.LawnchairDAO(window); + var crypto = new app.crypto.Crypto(window, util); + var cloudstorage = new app.dao.CloudStorage(window, $); + var devicestorage = new app.dao.DeviceStorage(util, crypto, jsonDao, null); + this.emailDao = new app.dao.EmailDAO(_, crypto, devicestorage, cloudstorage); -}); \ No newline at end of file + var loginView = new app.view.LoginView({ + dao: this.emailDao + }); + this.changePage(loginView); + }, + + compose: function() { + var composeView = new app.view.ComposeView(); + this.changePage(composeView); + }, + + folders: function(userId) { + var folderListView = new app.view.FolderListView({ + account: userId + }); + this.changePage(folderListView); + }, + + messagelist: function(userId, folder) { + var self = this; + var messageListView = new app.view.MessageListView({ + account: userId, + folder: folder, + dao: this.emailDao + }); + self.changePage(messageListView); + messageListView.loadItems(); + }, + + read: function(userId, folder, messageId) { + var readView = new app.view.ReadView({ + folder: folder, + messageId: decodeURIComponent(messageId), + dao: this.emailDao + }); + this.changePage(readView); + readView.renderBody(); + }, + + changePage: function(page) { + // render the page and append it to the DOM + var pageEl = $(page.el); + pageEl.attr('data-role', 'page'); + page.render(); + $('body').append(pageEl); + + // handle back click + pageEl.on('vmousedown', '#backBtn', function(e) { + e.preventDefault(); + window.history.back(); + }); + + // change page for link buttons on vmousedown instead of waiting on vmouseup + pageEl.on('vmousedown', 'a[data-role="button"]', function(e) { + e.preventDefault(); + var href = $(e.currentTarget).attr('href'); + window.location = href; + }); + + $.mobile.changePage(pageEl, { + changeHash: false, + reverse: false + }); + } + + }); + +}()); \ No newline at end of file diff --git a/src/js/jqm-config.js b/src/js/jqm-config.js index 0a7723c..e9bdb47 100644 --- a/src/js/jqm-config.js +++ b/src/js/jqm-config.js @@ -1,15 +1,18 @@ -'use strict'; +(function() { + 'use strict'; -$(document).on('mobileinit', function () { - console.log('mobileinit'); - $.mobile.ajaxEnabled = false; - $.mobile.linkBindingEnabled = false; - $.mobile.hashListeningEnabled = false; - $.mobile.pushStateEnabled = false; - $.mobile.defaultPageTransition = 'none'; + $(document).on('mobileinit', function() { + console.log('mobileinit'); + $.mobile.ajaxEnabled = false; + $.mobile.linkBindingEnabled = false; + $.mobile.hashListeningEnabled = false; + $.mobile.pushStateEnabled = false; + $.mobile.defaultPageTransition = 'none'; - // Remove page from DOM when it's being replaced - $(document).on('pagehide', 'div[data-role="page"]', function (event, ui) { - $(event.currentTarget).remove(); + // Remove page from DOM when it's being replaced + $(document).on('pagehide', 'div[data-role="page"]', function(event, ui) { + $(event.currentTarget).remove(); + }); }); -}); \ No newline at end of file + +}()); \ No newline at end of file diff --git a/src/js/loader.js b/src/js/loader.js index 448ae7a..f1bcb61 100644 --- a/src/js/loader.js +++ b/src/js/loader.js @@ -1,69 +1,71 @@ -'use strict'; +(function() { + 'use strict'; -/** - * The Template Loader. Used to asynchronously load templates located in separate .html files - */ -app.util.tpl = { + /** + * The Template Loader. Used to asynchronously load templates located in separate .html files + */ + app.util.tpl = { - // Hash of preloaded templates for the app - templates:{}, + // Hash of preloaded templates for the app + templates: {}, - // Recursively pre-load all the templates for the app. - loadTemplates:function (names, callback) { - var that = this; + // Recursively pre-load all the templates for the app. + loadTemplates: function(names, callback) { + var that = this; - var loadTemplate = function (index) { - var name = names[index]; - console.log('Loading template: ' + name); - $.get('tpl/' + name + '.html', function (data) { - that.templates[name] = data; - index++; - if (index < names.length) { - loadTemplate(index); - } else { - callback(); - } - }); - }; + var loadTemplate = function(index) { + var name = names[index]; + console.log('Loading template: ' + name); + $.get('tpl/' + name + '.html', function(data) { + that.templates[name] = data; + index++; + if (index < names.length) { + loadTemplate(index); + } else { + callback(); + } + }); + }; - loadTemplate(0); - }, + loadTemplate(0); + }, - // Get template by name from hash of preloaded templates - get:function (name) { - return this.templates[name]; - } + // Get template by name from hash of preloaded templates + get: function(name) { + return this.templates[name]; + } -}; - -/** - * Load templates and start the application - */ -$(document).ready(function() { - // are we running in native app or in browser? - var isBrowser = false; - if(document.URL.indexOf("http://") !== -1) { - isBrowser = true; - } - - if(!isBrowser) { - document.addEventListener("deviceready", onDeviceReady, false); - } else { - onDeviceReady(); - } - - function onDeviceReady() { - console.log('Starting in Browser: ' + isBrowser); - app.util.tpl.loadTemplates([ - 'login', - 'compose', - 'folderlist', - 'messagelist', - 'messagelistitem', - 'read' - ], function () { - var router = new AppRouter(); - Backbone.history.start(); - }); }; -}); \ No newline at end of file + + /** + * Load templates and start the application + */ + $(document).ready(function() { + // are we running in native app or in browser? + var isBrowser = false; + if (document.URL.indexOf("http://") !== -1) { + isBrowser = true; + } + + if (!isBrowser) { + document.addEventListener("deviceready", onDeviceReady, false); + } else { + onDeviceReady(); + } + + function onDeviceReady() { + console.log('Starting in Browser: ' + isBrowser); + app.util.tpl.loadTemplates([ + 'login', + 'compose', + 'folderlist', + 'messagelist', + 'messagelistitem', + 'read'], function() { + var router = new app.Router(); + Backbone.history.start(); + }); + } + }); + +}()); \ No newline at end of file diff --git a/src/manifest.webapp b/src/manifest.webapp new file mode 100644 index 0000000..d872cf9 --- /dev/null +++ b/src/manifest.webapp @@ -0,0 +1,15 @@ +{ + "name": "Whiteout", + "description": "HTML5 Mail with an encrypted message store both locally and in the cloud", + "launch_path": "/html5-mail/src/", + "icons": { + "128": "/html5-mail/src/css/images/key-128.png" + }, + "developer": { + "name": "Whiteout Networks GmbH", + "url": "http://whiteout.io" + }, + "appcache_path": "/html5-mail/src/manifest.appcache", + "csp": "script-src 'self' 'unsafe-eval'; object-src 'none'; style-src 'self' 'unsafe-inline'", + "default_locale": "en" +} \ No newline at end of file diff --git a/test/ecc-test.js b/src/test/ecc-test.js similarity index 100% rename from test/ecc-test.js rename to src/test/ecc-test.js diff --git a/test/integration/cloudstorage-dao-test.js b/src/test/integration/cloudstorage-dao-test.js similarity index 90% rename from test/integration/cloudstorage-dao-test.js rename to src/test/integration/cloudstorage-dao-test.js index 8e8a59f..94c8bff 100644 --- a/test/integration/cloudstorage-dao-test.js +++ b/src/test/integration/cloudstorage-dao-test.js @@ -7,19 +7,19 @@ var cloudstoragedao_test = { ivSize: 104 }; -asyncTest("Init", 1, function() { +asyncTest("Init", 1, function() { // init dependencies var util = new app.crypto.Util(window, uuid); var jsonDao = new app.dao.LawnchairDAO(window); cloudstoragedao_test.crypto = new app.crypto.Crypto(window, util); - cloudstoragedao_test.storage = new app.dao.DeviceStorage(cloudstoragedao_test.crypto, jsonDao, null); + cloudstoragedao_test.storage = new app.dao.DeviceStorage(util, cloudstoragedao_test.crypto, jsonDao, null); cloudstoragedao_test.cloudstorage = new app.dao.CloudStorage(window, $); cloudstoragedao_test.emailDao = new app.dao.EmailDAO(_, cloudstoragedao_test.crypto, cloudstoragedao_test.storage, cloudstoragedao_test.cloudstorage); - + // clear db before tests jsonDao.clear(function(err) { ok(!err, 'DB cleared. Error status: ' + err); - + start(); }); }); @@ -28,7 +28,7 @@ asyncTest("Init", 1, function() { asyncTest("Get user secret key from cloud", 1, function() { cloudstoragedao_test.cloudstorage.getUserSecretKey(cloudstoragedao_test.user, function(err) { ok(!err, 'Get/Sync key from cloud'); - + start(); }, function() { cloudstoragedao_test.storage.clear(function(err) { @@ -42,21 +42,21 @@ asyncTest("Get user secret key from cloud", 1, function() { asyncTest("Persist user secret key to cloud", 1, function() { cloudstoragedao_test.cloudstorage.persistUserSecretKey(cloudstoragedao_test.user, function(err) { ok(!err, 'Persist key to cloud'); - + start(); }); }); asyncTest("Sync emails from cloud", 3, function() { - + var account = new app.model.Account({ emailAddress: cloudstoragedao_test.user, symKeySize: cloudstoragedao_test.keySize, symIvSize: cloudstoragedao_test.ivSize }); - + cloudstoragedao_test.emailDao.init(account, cloudstoragedao_test.password, function() { - ok(true, 'Init complete'); + ok(true, 'Init complete'); cloudstoragedao_test.emailDao.syncFromCloud('inbox', function(res) { ok(!res, 'Synced items'); diff --git a/src/test/integration/index.html b/src/test/integration/index.html new file mode 100644 index 0000000..25cefdc --- /dev/null +++ b/src/test/integration/index.html @@ -0,0 +1,74 @@ + + + + + JavaScript Integration Tests + + + +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/test/pgp-test.js b/src/test/pgp-test.js similarity index 100% rename from test/pgp-test.js rename to src/test/pgp-test.js diff --git a/test/qunit-1.11.0.css b/src/test/qunit-1.11.0.css similarity index 100% rename from test/qunit-1.11.0.css rename to src/test/qunit-1.11.0.css diff --git a/test/qunit-1.11.0.js b/src/test/qunit-1.11.0.js similarity index 100% rename from test/qunit-1.11.0.js rename to src/test/qunit-1.11.0.js diff --git a/test/test-data.js b/src/test/test-data.js similarity index 100% rename from test/test-data.js rename to src/test/test-data.js diff --git a/test/unit/aes-test.js b/src/test/unit/aes-test.js similarity index 100% rename from test/unit/aes-test.js rename to src/test/unit/aes-test.js diff --git a/test/unit/crypto-test.js b/src/test/unit/crypto-test.js similarity index 100% rename from test/unit/crypto-test.js rename to src/test/unit/crypto-test.js diff --git a/test/unit/devicestorage-test.js b/src/test/unit/devicestorage-test.js similarity index 100% rename from test/unit/devicestorage-test.js rename to src/test/unit/devicestorage-test.js diff --git a/test/unit/email-dao-test.js b/src/test/unit/email-dao-test.js similarity index 91% rename from test/unit/email-dao-test.js rename to src/test/unit/email-dao-test.js index d5d360b..e07f619 100644 --- a/test/unit/email-dao-test.js +++ b/src/test/unit/email-dao-test.js @@ -7,7 +7,7 @@ var emaildao_test = { ivSize: 104 }; -asyncTest("Init", 2, function() { +asyncTest("Init", 2, function() { // init dependencies var util = new app.crypto.Util(window, uuid); var jsonDao = new app.dao.LawnchairDAO(window); @@ -15,55 +15,57 @@ asyncTest("Init", 2, function() { emaildao_test.storage = new app.dao.DeviceStorage(util, emaildao_test.crypto, jsonDao, null); // cloud storage stub var cloudstorageStub = { - getUserSecretKey: function(emailAdress, callback) { callback(); } + getUserSecretKey: function(emailAdress, callback) { + callback(); + } }; emaildao_test.emailDao = new app.dao.EmailDAO(_, emaildao_test.crypto, emaildao_test.storage, cloudstorageStub); - + // generate test data emaildao_test.list = new TestData().getEmailCollection(100); - + var account = new app.model.Account({ emailAddress: emaildao_test.user, symKeySize: emaildao_test.keySize, symIvSize: emaildao_test.ivSize }); - + emaildao_test.emailDao.init(account, emaildao_test.password, function() { equal(emaildao_test.emailDao.account.get('emailAddress'), emaildao_test.user, 'Email DAO Account'); - + // clear db before tests jsonDao.clear(function(err) { ok(!err, 'DB cleared. Error status: ' + err); - + start(); }); }); }); -asyncTest("Persist test emails", 2, function() { +asyncTest("Persist test emails", 2, function() { emaildao_test.crypto.aesEncryptListForUser(emaildao_test.list.toJSON(), function(encryptedList) { equal(encryptedList.length, emaildao_test.list.length, 'Encrypt list'); - + // add sent date to encrypted items for (var i = 0; i < encryptedList.length; i++) { encryptedList[i].sentDate = emaildao_test.list.at(i).get('sentDate'); } - + emaildao_test.storage.storeEcryptedList(encryptedList, 'email_inbox', function() { ok(true, 'Store encrypted list'); start(); }); - }); + }); }); asyncTest("List Email models", 1, function() { emaildao_test.emailDao.listItems('inbox', 0, emaildao_test.list.length, function(collection) { var gotten = collection.toJSON(), reference = emaildao_test.list.toJSON(); - + deepEqual(gotten, reference, 'Compare collection'); - + start(); }); }); @@ -73,4 +75,4 @@ asyncTest("Get item", 1, function() { var mail = emaildao_test.emailDao.getItem('inbox', item.id); deepEqual(mail.toJSON(), item, 'Item correct'); start(); -}); +}); \ No newline at end of file diff --git a/src/test/unit/index.html b/src/test/unit/index.html new file mode 100644 index 0000000..1769833 --- /dev/null +++ b/src/test/unit/index.html @@ -0,0 +1,81 @@ + + + + + JavaScript Unit Tests + + + +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/test/unit/lawnchair-dao-test.js b/src/test/unit/lawnchair-dao-test.js similarity index 100% rename from test/unit/lawnchair-dao-test.js rename to src/test/unit/lawnchair-dao-test.js diff --git a/test/unit/localstorage-dao-test.js b/src/test/unit/localstorage-dao-test.js similarity index 100% rename from test/unit/localstorage-dao-test.js rename to src/test/unit/localstorage-dao-test.js diff --git a/test/unit/util-test.js b/src/test/unit/util-test.js similarity index 100% rename from test/unit/util-test.js rename to src/test/unit/util-test.js diff --git a/test/integration/index.html b/test/integration/index.html deleted file mode 100644 index e0f4085..0000000 --- a/test/integration/index.html +++ /dev/null @@ -1,74 +0,0 @@ - - - - - JavaScript Integration Tests - - - -
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/test/unit/index.html b/test/unit/index.html deleted file mode 100644 index 7c44b7f..0000000 --- a/test/unit/index.html +++ /dev/null @@ -1,81 +0,0 @@ - - - - - JavaScript Unit Tests - - - -
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file