diff --git a/src/index.html b/src/index.html index 74de1ae..0771090 100644 --- a/src/index.html +++ b/src/index.html @@ -6,10 +6,6 @@ - - diff --git a/src/js/app-config.js b/src/js/app-config.js index 41e3d2f..5224acb 100644 --- a/src/js/app-config.js +++ b/src/js/app-config.js @@ -32,12 +32,26 @@ define(function(require) { 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 + }, + smtp: { + host: 'smtp.wmail.io', + port: 465, + secure: true + } + }, gmail: { clientId: clientId || '440907777130.apps.googleusercontent.com', imap: { diff --git a/src/js/app-controller.js b/src/js/app-controller.js index 71d4d80..3011948 100644 --- a/src/js/app-controller.js +++ b/src/js/app-controller.js @@ -17,6 +17,7 @@ define(function(require) { 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'), @@ -102,6 +103,7 @@ define(function(require) { 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)); emailDao.onError = self.onError; }; diff --git a/src/js/controller/add-account.js b/src/js/controller/add-account.js index 2530a41..ac1e982 100644 --- a/src/js/controller/add-account.js +++ b/src/js/controller/add-account.js @@ -1,7 +1,8 @@ define(function(require) { 'use strict'; - var appCtrl = require('js/app-controller'); + var appCtrl = require('js/app-controller'), + cfg = require('js/app-config').config; var AddAccountCtrl = function($scope, $location, $routeParams) { if (!appCtrl._auth && !$routeParams.dev) { @@ -9,6 +10,57 @@ define(function(require) { return; } + $scope.step = 1; + + $scope.goTo = function(step) { + $scope.step = step; + }; + + $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, + //betaCode: $scope.betaCode + }, function(err) { + if (err) { + $scope.busy = false; + $scope.errMsg = 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()) { @@ -31,39 +83,9 @@ define(function(require) { }); }; - $scope.connectToYahoo = function() { + $scope.connectTo = function(provider) { $location.path('/login-set-credentials').search({ - provider: 'yahoo' - }); - }; - - $scope.connectToTonline = function() { - $location.path('/login-set-credentials').search({ - provider: 'tonline' - }); - }; - - $scope.connectToOutlook = function() { - $location.path('/login-set-credentials').search({ - provider: 'outlook' - }); - }; - - $scope.connectToGmx = function() { - $location.path('/login-set-credentials').search({ - provider: 'gmx' - }); - }; - - $scope.connectToWebde = function() { - $location.path('/login-set-credentials').search({ - provider: 'webde' - }); - }; - - $scope.connectOther = function() { - $location.path('/login-set-credentials').search({ - provider: 'custom' + provider: provider }); }; }; diff --git a/src/js/dao/admin-dao.js b/src/js/dao/admin-dao.js new file mode 100644 index 0000000..79ded1b --- /dev/null +++ b/src/js/dao/admin-dao.js @@ -0,0 +1,38 @@ +define(function() { + 'use strict'; + + 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; + + 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; + } + + callback(); + }); + }; + + return AdminDAO; +}); \ No newline at end of file diff --git a/src/manifest.json b/src/manifest.json index fb83e65..aec0803 100644 --- a/src/manifest.json +++ b/src/manifest.json @@ -17,6 +17,7 @@ "notifications", "https://keys-test.whiteout.io/", "https://keychain-test.whiteout.io/", + "https://admin-node.whiteout.io/", "https://www.googleapis.com/", "identity", { "socket": [ diff --git a/src/sass/views/_add-account.scss b/src/sass/views/_add-account.scss index 7f70f72..a6be8e9 100644 --- a/src/sass/views/_add-account.scss +++ b/src/sass/views/_add-account.scss @@ -1,144 +1,191 @@ .view-add-account { - height: 100%; - background-color: $color-grey-lightest; - color: $color-grey-dark; - overflow-y: auto; - h1 { - padding: 60px 0 50px 0; - text-align: center; - margin: 0; - font-size: 32px; - line-height: 64px; + .choose { + .choice { + padding: 20px; - @include respond-to(mobile) { - padding: 30px 0 20px 0; + p { + margin-top: 0; + } + } + + hr { + margin: 30px 0; + color: $color-grey-lighter; } } - ul { - list-style-type: none; - width: 320px; - margin: 0 auto 30px auto; - padding: 0; - border-width: 1px; - border-style: solid; - border-color: $color-grey-lighter; + .create-account { + div.flex { + width: 100%; + margin: 0; + display: flex; - li { - position: relative; - height: 68px; - cursor: pointer; - &:hover, - &:focus { - opacity: 0.6; - } - &:active { - opacity: 1; - top: 1px; - left: 1px; + input[type="text"].wmail { + flex-grow: 1; } - &:nth-child(odd) { - background-color: white; - } - - &:nth-child(even) { - background-color: $color-grey-lightest; + .domain { + font-size: $font-size-bigger; + margin: 0; + height: 38px; + line-height: 38px; + flex-shrink: 0; + + @include respond-to(mobile) { + font-size: $font-size-big; + } } - &.google { - div { - width: 164px; - height: 58px; - margin: 0px auto; - padding: 8px 0; + input[type="password"] { + flex: 1; + } - img { + input[type="password"].right { + flex: 1; + margin-right: 0; + } + } + } + + .providers { + h1 { + padding: 60px 0 50px 0; + text-align: center; + margin: 0; + font-size: 32px; + line-height: 64px; + + @include respond-to(mobile) { + padding: 30px 0 20px 0; + } + } + + ul { + list-style-type: none; + max-width: 320px; + width: 100%; + margin: 0 auto 30px auto; + padding: 0; + border-width: 1px; + border-style: solid; + border-color: $color-grey-lighter; + + li { + position: relative; + height: 68px; + cursor: pointer; + &:hover, + &:focus { + opacity: 0.6; + } + &:active { + opacity: 1; + top: 1px; + left: 1px; + } + + &:nth-child(odd) { + background-color: white; + } + + &:nth-child(even) { + background-color: $color-grey-lightest; + } + + &.google { + div { width: 164px; height: 58px; + margin: 0px auto; + padding: 8px 0; + + img { + width: 164px; + height: 58px; + } } } - } - &.whiteout { - div { - width: 210px; - margin: 0 auto; - padding: 15px 0; + &.whiteout { + div { + width: 210px; + margin: 0 auto; + padding: 15px 0; - img { - width: 100%; + img { + width: 100%; + } } } - } - &.yahoo { - div { - width: 181px; - margin: 0 auto; + &.yahoo { + div { + width: 181px; + margin: 0 auto; - img { - width: 100%; + img { + width: 100%; + } } } - } - &.tonline { - div { - width: 271px; - margin: 0 auto; + &.tonline { + div { + width: 271px; + margin: 0 auto; - img { - width: 100%; + img { + width: 100%; + } } } - } - &.outlook { - div { - width: 256px; - margin: 0 auto; - padding: 13px 0; + &.outlook { + div { + width: 256px; + margin: 0 auto; + padding: 13px 0; - img { - width: 100%; + img { + width: 100%; + } } } - } - &.gmx { - div { - width: 115px; - margin: 0 auto; - padding: 13px 0; - - img { + &.gmx { + div { width: 115px; - height: 37px; + margin: 0 auto; + padding: 13px 0; + + img { + width: 115px; + height: 37px; + } } } - } - &.webde { - div { - width: 137px; - margin: 0 auto; - padding: 17px 0; - - img { + &.webde { + div { width: 137px; - height: 31px; + margin: 0 auto; + padding: 17px 0; + + img { + width: 137px; + height: 31px; + } } } - } - &.other { - h3 { - margin: 0; - line-height: 68px; - font-size: 21px; - text-align: center; + &.other { + h3 { + margin: 0; + line-height: 68px; + font-size: 21px; + text-align: center; + } } } } diff --git a/src/sass/views/_dialog.scss b/src/sass/views/_dialog.scss index 370230e..40ee77c 100644 --- a/src/sass/views/_dialog.scss +++ b/src/sass/views/_dialog.scss @@ -14,6 +14,10 @@ .control { button { min-width: 100px; + + &.ng-animate { + transition: none; + } } } } \ No newline at end of file diff --git a/src/sass/views/_login.scss b/src/sass/views/_login.scss index ddc8558..bbdd69f 100644 --- a/src/sass/views/_login.scss +++ b/src/sass/views/_login.scss @@ -69,6 +69,7 @@ margin-right: 10px; } label, + input[type="tel"], input[type="text"], input[type="email"], input[type="number"], diff --git a/src/tpl/add-account.html b/src/tpl/add-account.html index 33fe96e..fd4e764 100644 --- a/src/tpl/add-account.html +++ b/src/tpl/add-account.html @@ -1,33 +1,86 @@ -
Create Whiteout account. Create a new fully encrypted Whiteout Mailbox.
+ +Login to IMAP account. Connect Whiteout Mail to any existing email account via IMAP.
+ +Create Whiteout account. Please fill out the following form. You will need a beta access code during the private beta period.
+ +Connect Whiteout Mail to your fully encrypted Whiteout Mailbox (hosted in Europe).
Incoming cleartext messages are encrypted with your public PGP key before being stored in your inbox.
diff --git a/test/unit/add-account-ctrl-test.js b/test/unit/add-account-ctrl-test.js index 00fe15f..31639e2 100644 --- a/test/unit/add-account-ctrl-test.js +++ b/test/unit/add-account-ctrl-test.js @@ -6,15 +6,17 @@ define(function(require) { 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'); describe('Add Account Controller unit test', function() { - var scope, location, ctrl, authStub, origAuth; + 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); angular.module('addaccounttest', []); mocks.module('addaccounttest'); @@ -22,6 +24,7 @@ define(function(require) { location = $location; scope = $rootScope.$new(); scope.state = {}; + scope.form = {}; sinon.stub(location, 'path').returns(location); sinon.stub(location, 'search').returns(location); @@ -41,11 +44,61 @@ define(function(require) { location.path.restore(); location.search.restore(); - scope.$apply.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; + 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; + adminStub.createUser.yieldsAsync(); + + scope.login = function() { + expect(scope.busy).to.be.true; + expect(scope.errMsg).to.be.undefined; + expect(adminStub.createUser.calledOnce).to.be.true; + done(); + }; + + scope.createWhiteoutAccount(); + expect(scope.busy).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() { @@ -101,70 +154,17 @@ define(function(require) { }); }); - describe('connectToYahoo', function() { + describe('connectTo', function() { it('should forward to login', function() { - scope.connectToYahoo(); + var provider = 'wmail'; + scope.connectTo(provider); expect(location.path.calledWith('/login-set-credentials')).to.be.true; expect(location.search.calledWith({ - provider: 'yahoo' + provider: provider })).to.be.true; }); }); - describe('connectToTonline', function() { - it('should forward to login', function() { - scope.connectToTonline(); - - expect(location.path.calledWith('/login-set-credentials')).to.be.true; - expect(location.search.calledWith({ - provider: 'tonline' - })).to.be.true; - }); - }); - - describe('connectToOutlook', function() { - it('should forward to login', function() { - scope.connectToOutlook(); - - expect(location.path.calledWith('/login-set-credentials')).to.be.true; - expect(location.search.calledWith({ - provider: 'outlook' - })).to.be.true; - }); - }); - - describe('connectToGmx', function() { - it('should forward to login', function() { - scope.connectToGmx(); - - expect(location.path.calledWith('/login-set-credentials')).to.be.true; - expect(location.search.calledWith({ - provider: 'gmx' - })).to.be.true; - }); - }); - - describe('connectToWebde', function() { - it('should forward to login', function() { - scope.connectToWebde(); - - expect(location.path.calledWith('/login-set-credentials')).to.be.true; - expect(location.search.calledWith({ - provider: 'webde' - })).to.be.true; - }); - }); - - describe('connectOther', function() { - it('should forward to login', function() { - scope.connectOther(); - - expect(location.path.calledWith('/login-set-credentials')).to.be.true; - expect(location.search.calledWith({ - provider: 'custom' - })).to.be.true; - }); - }); }); }); \ No newline at end of file diff --git a/test/unit/admin-dao-test.js b/test/unit/admin-dao-test.js new file mode 100644 index 0000000..708b1b5 --- /dev/null +++ b/test/unit/admin-dao-test.js @@ -0,0 +1,83 @@ +define(function(require) { + 'use strict'; + + var RestDAO = require('js/dao/rest-dao'), + AdminDAO = require('js/dao/admin-dao'), + expect = chai.expect; + + describe('Admin DAO unit tests', function() { + + var adminDao, restDaoStub, + emailAddress = 'test@example.com', + password = 'secret'; + + beforeEach(function() { + restDaoStub = sinon.createStubInstance(RestDAO); + adminDao = new AdminDAO(restDaoStub); + }); + + afterEach(function() {}); + + 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 + }; + + 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 + }; + + 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 + }; + + restDaoStub.post.withArgs(opt, '/user').yields(); + + adminDao.createUser(opt, function(err) { + expect(err).to.not.exist; + expect(restDaoStub.post.calledOnce).to.be.true; + done(); + }); + }); + }); + + }); + +}); \ No newline at end of file diff --git a/test/unit/main.js b/test/unit/main.js index 63c494d..e51935e 100644 --- a/test/unit/main.js +++ b/test/unit/main.js @@ -79,6 +79,7 @@ function startTests() { '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',