1
0
mirror of https://github.com/moparisthebest/mail synced 2024-11-22 08:52:15 -05:00

[WO-577]: implement wmail account creation

This commit is contained in:
Tankred Hase 2014-09-15 17:09:13 +02:00
parent d31c8a69e6
commit 7c7d650cf2
13 changed files with 488 additions and 226 deletions

View File

@ -6,10 +6,6 @@
<!-- Theses CSP rules are used as a fallback in runtimes such as Cordova -->
<meta http-equiv="Content-Security-Policy" content="default-src 'self' chrome-extension: file: gap:; object-src 'none'; script-src 'self' 'unsafe-eval' chrome-extension: file: gap:; connect-src *; style-src 'self' 'unsafe-inline' chrome-extension: file: gap:; img-src 'self' chrome-extension: file: gap: data:">
<!-- Test CSP with script-src 'unsafe-inline' and warn user -->
<script type="text/javascript">
alert('Warning! Your browser does not support Content-Security-Policy (CSP). If possible please switch to a different browser.');
</script>
<!-- iOS homescreen link -->
<meta name="apple-mobile-web-app-capable" content="yes">

View File

@ -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: {

View File

@ -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;
};

View File

@ -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
});
};
};

38
src/js/dao/admin-dao.js Normal file
View File

@ -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;
});

View File

@ -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": [

View File

@ -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;
}
}
}
}

View File

@ -14,6 +14,10 @@
.control {
button {
min-width: 100px;
&.ng-animate {
transition: none;
}
}
}
}

View File

@ -69,6 +69,7 @@
margin-right: 10px;
}
label,
input[type="tel"],
input[type="text"],
input[type="email"],
input[type="number"],

View File

@ -1,33 +1,86 @@
<div class="view-add-account">
<div class="view-login view-add-account">
<h1>Select email account</h1>
<div ng-show="step === 1">
<div class="logo">
<img src="img/whiteout_logo.svg" alt="whiteout.io">
</div><!--/logo-->
<ul>
<li class="whiteout disabled" popover="#whiteout-info" wo-touch="onError({message:'Whiteout Mailbox coming soon!', sync:true})">
<div><img src="img/whiteout_logo.svg" alt="Whiteout Mailbox"></div>
</li>
<li class="google" popover="#google-info" wo-touch="connectToGoogle()">
<div><img src="img/google_logo.png" alt="Google Mail"></div>
</li>
<li class="outlook" popover="#outlook-info" wo-touch="connectToOutlook()">
<div><img src="img/outlook_logo.jpg" alt="Outlook.com"></div>
</li>
<li class="yahoo" popover="#yahoo-info" wo-touch="connectToYahoo()">
<div><img src="img/yahoo_logo.png" alt="Yahoo! Mail"></div>
</li>
<li class="gmx" popover="#gmx-info" wo-touch="connectToGmx()">
<div><img src="img/gmx_logo.jpg" alt="GMX.net"></div>
</li>
<li class="webde" popover="#webde-info" wo-touch="connectToWebde()">
<div><img src="img/webde_logo.jpg" alt="Web.de"></div>
</li>
<li class="tonline" popover="#tonline-info" wo-touch="connectToTonline()">
<div><img src="img/tonline_logo.jpg" alt="T-Online"></div>
</li>
<li class="other" popover="#custom-info" wo-touch="connectOther()">
<h3>Custom server...</h3>
</li>
</ul>
<div class="content choose">
<div class="choice">
<p><b>Create Whiteout account.</b> Create a new fully encrypted Whiteout Mailbox.</p>
<button class="btn" wo-touch="goTo(2)">Create new account</button>
</div>
<hr>
<div class="choice">
<p><b>Login to IMAP account.</b> Connect Whiteout Mail to any existing email account via IMAP.</p>
<button class="btn btn-alt" wo-touch="goTo(3)">Login to existing</button>
</div>
</div>
</div>
<div ng-show="step === 2">
<div class="logo">
<img src="img/whiteout_logo.svg" alt="whiteout.io">
</div><!--/logo-->
<div class="content create-account">
<p><b>Create Whiteout account.</b> Please fill out the following form. You will need a <i>beta access code</i> during the private beta period.</p>
<form name="form">
<label class="input-error-message" ng-show="errMsg">{{errMsg}}</label>
<label class="input-error-message" ng-show="form.$invalid">Please fill out all required fields!</label>
<div class="flex">
<input class="input-text wmail" ng-model="user" required type="text" pattern='^(([^<>()[\]\\.,;:\s@\"]+(\.[^<>()[\]\\.,;:\s@\"]+)*)|(\".+\"))' placeholder="User name">
<span class="domain">@wmail.io</span>
</div>
<input class="input-text" type="text" ng-model="realname" placeholder="Full name (optional)"></input>
<div class="flex">
<input class="input-text" ng-model="pass" required type="password" placeholder="Password" pattern=".{3,30}">
<input class="input-text right" ng-model="confirmPass" required type="password" placeholder="Confirm password" ng-class="{'input-text-error': (pass || confirmPass) && pass !== confirmPass}">
</div>
<input class="input-text" type="tel" ng-model="phone" required placeholder="Mobile phone number (e.g. +49 170 1234567)"/>
<input class="input-text" type="text" class="input-text" ng-model="betaCode" required placeholder="Beta access code">
<div class="working" ng-show="busy">
<span class="spinner"></span>
</div>
<div>
<button class="btn" type="submit" ng-click="createWhiteoutAccount()">Create</button>
</div>
</form>
</div>
</div>
<div class="providers" ng-show="step === 3">
<h1>Select account</h1>
<ul>
<li class="whiteout" popover="#whiteout-info" wo-touch="connectTo('wmail')">
<div><img src="img/whiteout_logo.svg" alt="Whiteout Mailbox"></div>
</li>
<li class="google" popover="#google-info" wo-touch="connectToGoogle()">
<div><img src="img/google_logo.png" alt="Google Mail"></div>
</li>
<li class="outlook" popover="#outlook-info" wo-touch="connectTo('outlook')">
<div><img src="img/outlook_logo.jpg" alt="Outlook.com"></div>
</li>
<li class="yahoo" popover="#yahoo-info" wo-touch="connectTo('yahoo')">
<div><img src="img/yahoo_logo.png" alt="Yahoo! Mail"></div>
</li>
<li class="gmx" popover="#gmx-info" wo-touch="connectTo('gmx')">
<div><img src="img/gmx_logo.jpg" alt="GMX.net"></div>
</li>
<li class="webde" popover="#webde-info" wo-touch="connectTo('webde')">
<div><img src="img/webde_logo.jpg" alt="Web.de"></div>
</li>
<li class="tonline" popover="#tonline-info" wo-touch="connectTo('tonline')">
<div><img src="img/tonline_logo.jpg" alt="T-Online"></div>
</li>
<li class="other" popover="#custom-info" wo-touch="connectTo('custom')">
<h3>Custom server...</h3>
</li>
</ul>
</div>
</div>
@ -44,7 +97,7 @@
<div id="whiteout-info" class="popover right desktop-only" ng-controller="PopoverCtrl">
<div class="arrow"></div>
<div class="popover-title"><b>Whiteout Mailbox (coming soon)</b></div>
<div class="popover-title"><b>Whiteout Mailbox</b></div>
<div class="popover-content">
<p>Connect Whiteout Mail to your fully encrypted Whiteout Mailbox (hosted in Europe).</p>
<p>Incoming cleartext messages are encrypted with your public PGP key before being stored in your inbox.</p>

View File

@ -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;
});
});
});
});

View File

@ -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();
});
});
});
});
});

View File

@ -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',