[WO-233] Implement opt-in for terms of service

This commit is contained in:
Tankred Hase 2014-04-15 17:43:33 +02:00
parent 680ed6e0c3
commit becff37b4b
18 changed files with 161 additions and 26 deletions

View File

@ -59,8 +59,8 @@ Then visit [http://localhost:8580/dist/chrome.html#/desktop](http://localhost:85
We work together with existing open source projects wherever possible and contribute any changes we make back upstream. Many of theses libraries are licensed under an open source license. Here are some of them:
* [OpenPGP.js](http://openpgpjs.org): An implementation of OpenPGP in Javascript
* [Inbox](https://github.com/andris9/inbox): Simple IMAP client for node.js
* [Nodemailer](http://www.nodemailer.com): SMTP client for node.js
* [Forge](https://github.com/digitalbazaar/forge): An implementation of TLS in Javascript
* [node-shims](https://github.com/whiteout-io/node-shims): Shims for wrapping node's net/tls (TCP socket) APIs around chrome.socket
* [OpenPGP.js](http://openpgpjs.org) (LGPL license): An implementation of OpenPGP in Javascript
* [Inbox](https://github.com/andris9/inbox) (MIT license): Simple IMAP client for node.js
* [Nodemailer](http://www.nodemailer.com) (MIT license): SMTP client for node.js
* [Forge](https://github.com/digitalbazaar/forge) (BSD license): An implementation of TLS in Javascript
* [node-shims](https://github.com/whiteout-io/node-shims) (MIT license): Shims for wrapping node's net/tls (TCP socket) APIs around chrome.socket

View File

@ -3,7 +3,7 @@ define(function(require) {
var _ = require('underscore'),
app = {},
cloudUrl, clientId;
appVersion, cloudUrl, clientId;
// parse manifest to get configurations for current runtime
try {
@ -16,6 +16,8 @@ define(function(require) {
cloudUrl = cloudUrl.substring(0, cloudUrl.length - 1);
// get client ID for OAuth requests
clientId = manifest.oauth2.client_id;
// get the app version
appVersion = manifest.version;
} catch (e) {}
/**
@ -44,7 +46,8 @@ define(function(require) {
iconPath: '/img/icon.png',
verificationUrl: '/verify/',
verificationUuidLength: 36,
dbVersion: 1
dbVersion: 1,
appVersion: appVersion
};
/**

View File

@ -9,6 +9,7 @@ requirejs([
'js/controller/account',
'js/controller/set-passphrase',
'js/controller/contacts',
'js/controller/about',
'js/controller/login',
'js/controller/login-initial',
'js/controller/login-new-device',
@ -29,6 +30,7 @@ requirejs([
AccountCtrl,
SetPassphraseCtrl,
ContactsCtrl,
AboutCtrl,
LoginCtrl,
LoginInitialCtrl,
LoginNewDeviceCtrl,
@ -96,6 +98,7 @@ requirejs([
app.controller('AccountCtrl', AccountCtrl);
app.controller('SetPassphraseCtrl', SetPassphraseCtrl);
app.controller('ContactsCtrl', ContactsCtrl);
app.controller('AboutCtrl', AboutCtrl);
app.controller('DialogCtrl', DialogCtrl);
app.controller('PopoverCtrl', PopoverCtrl);

View File

@ -0,0 +1,32 @@
define(function(require) {
'use strict';
var cfg = require('js/app-config').config;
//
// Controller
//
var AboutCtrl = function($scope) {
$scope.state.about = {
open: false,
toggle: function(to) {
this.open = to;
}
};
//
// scope variables
//
$scope.version = cfg.appVersion;
$scope.date = new Date();
//
// scope functions
//
};
return AboutCtrl;
});

View File

@ -6,7 +6,7 @@ define(function(require) {
var LoginInitialCtrl = function($scope, $location) {
var emailDao = appController._emailDao,
states;
states, termsMsg = 'You must accept the Terms of Service to continue.';
// global state... inherited to all child scopes
$scope.$root.state = {};
@ -25,6 +25,13 @@ define(function(require) {
//
$scope.importKey = function() {
if (!$scope.state.agree) {
$scope.onError({
message: termsMsg
});
return;
}
$location.path('/login-new-device');
};
@ -89,6 +96,13 @@ define(function(require) {
return;
}
if (!$scope.state.agree) {
$scope.onError({
message: termsMsg
});
return;
}
$scope.setState(states.PROCESSING);
setTimeout(function() {
emailDao.unlock({

View File

@ -27,6 +27,7 @@
@import "views/account";
@import "views/set-passphrase";
@import "views/contacts";
@import "views/about";
@import "views/dialog";
@import "views/navigation";
@import "views/mail-list";

View File

@ -2,11 +2,6 @@
padding: 0px;
color: $color-grey-dark;
@include respond-to(mobile) {
top: 0;
max-width: 100%;
}
.control {
float: right;

View File

@ -0,0 +1,15 @@
.view-about {
@include respond-to(desktop) {
max-width: 550px;
}
.content {
margin: 30px auto;
text-align: center;
a {
color: $color-blue;
}
}
}

View File

@ -1,7 +1,10 @@
.view-dialog {
max-width: 350px;
height: auto;
top: 30%;
@include respond-to(desktop) {
max-width: 350px;
top: 30%;
}
p {
text-align: center;

View File

@ -24,12 +24,12 @@
margin-left: auto;
margin-right: auto;
b, a {
color: $color-blue;
}
p {
line-height: 150%;
b, a {
color: $color-blue;
}
}
div {
@ -69,6 +69,13 @@
.passphrase-label-ok {
color: green;
}
.opt-in-terms {
.checkbox-wrapper {
margin-top: 0;
float: left;
}
}
}
}
@ -76,7 +83,9 @@
}
.view-login-initial {
button {
margin-right: 10px;
.content {
button {
margin-right: 10px;
}
}
}

15
src/tpl/about.html Normal file
View File

@ -0,0 +1,15 @@
<div class="lightbox-body" ng-controller="AboutCtrl">
<header>
<button class="close" ng-click="state.about.toggle(false)" data-action="lightbox-close">&#xe007;</button>
</header>
<div class="content">
<img src="img/icon.png">
<h2>Whiteout Mail</h2>
<p>Version {{version}}</p>
<p>&copy; {{ date | date:'yyyy'}} Whiteout Networks GmbH</p>
<p><a href="https://whiteout.io/imprint.html" target="_blank">Imprint</a> &middot; <a href="https://whiteout.io/privacy.html" target="_blank">Privacy</a> &middot; <a href="https://whiteout.io/terms.html" target="_blank">Terms</a> &middot; <a href="https://github.com/whiteout-io/mail-html5#license" target="_blank">Licenses</a></p>
</div><!-- /.content -->
</div><!-- /.lightbox-body -->

View File

@ -31,6 +31,9 @@
<div class="lightbox-overlay" ng-class="{'show': state.contacts.open}">
<div class="lightbox lightbox-effect" ng-include="'tpl/contacts.html'"></div>
</div>
<div class="lightbox-overlay" ng-class="{'show': state.about.open}">
<div class="lightbox lightbox-effect view-about" ng-include="'tpl/about.html'"></div>
</div>
<div class="lightbox-overlay" ng-class="{'show': state.dialog.open}">
<div class="lightbox lightbox-effect dialog view-dialog" ng-include="'tpl/dialog.html'"></div>
</div>

View File

@ -20,7 +20,7 @@
<!-- lightbox -->
<div class="lightbox-overlay" ng-class="{'show': state.dialog.open}">
<div class="lightbox lightbox-effect view-dialog" ng-include="'tpl/dialog.html'"></div>
<div class="lightbox lightbox-effect dialog view-dialog" ng-include="'tpl/dialog.html'"></div>
</div>
<!-- popovers -->

View File

@ -14,6 +14,12 @@
<input class="input-text" type="password" ng-model="state.confirmation" ng-class="{'input-text-error': (state.confirmation || state.passphrase) && state.confirmation !== state.passphrase}" placeholder="Confirm passphrase" tabindex="2">
<span class="popover-info" data-icon-append="&#xe010;" popover="#passphrase-info"></span>
</div>
<div class="opt-in-terms">
<div class="checkbox-wrapper"><input type="checkbox" ng-model="state.agree"></div>
<div>I agree to the Whiteout Networks <a href="https://whiteout.io/terms.html" target="_blank">Terms of Service</a> and have read the <a href="https://whiteout.io/privacy.html" target="_blank">Privacy Policy</a>.</div>
</div>
<div>
<button type="submit" ng-click="confirmPassphrase()" class="btn" ng-disabled="(state.confirmation || state.passphrase) && state.confirmation !== state.passphrase" tabindex="3">Continue</button>
</div>
@ -29,7 +35,7 @@
<!-- lightbox -->
<div class="lightbox-overlay" ng-class="{'show': state.dialog.open}">
<div class="lightbox lightbox-effect view-dialog" ng-include="'tpl/dialog.html'"></div>
<div class="lightbox lightbox-effect dialog view-dialog" ng-include="'tpl/dialog.html'"></div>
</div>
<!-- popovers -->

View File

@ -22,7 +22,7 @@
<!-- lightbox -->
<div class="lightbox-overlay" ng-class="{'show': state.dialog.open}">
<div class="lightbox lightbox-effect view-dialog" ng-include="'tpl/dialog.html'"></div>
<div class="lightbox lightbox-effect dialog view-dialog" ng-include="'tpl/dialog.html'"></div>
</div>
<!-- popovers -->

View File

@ -12,5 +12,5 @@
<!-- lightbox -->
<div class="lightbox-overlay" ng-class="{'show': state.dialog.open}">
<div class="lightbox lightbox-effect view-dialog" ng-include="'tpl/dialog.html'"></div>
<div class="lightbox lightbox-effect dialog view-dialog" ng-include="'tpl/dialog.html'"></div>
</div>

View File

@ -17,7 +17,7 @@
<ul class="nav-secondary">
<li><a href="#" ng-click="state.account.toggle(true); $event.preventDefault()">Account</a></li>
<li><a href="#" ng-click="state.contacts.toggle(true); $event.preventDefault()">Contacts</a></li>
<li><a href="http://whiteout.io" target="_blank">About</a></li>
<li><a href="#" ng-click="state.about.toggle(true); $event.preventDefault()">About</a></li>
</ul>
<footer>

View File

@ -58,6 +58,25 @@ define(function(require) {
});
});
describe('go to import key', function() {
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');
done();
};
scope.importKey();
});
it('should work', function() {
scope.state.agree = true;
scope.importKey();
expect(location.$$path).to.equal('/login-new-device');
});
});
describe('check passphrase quality', function() {
it('should be too short', function() {
scope.state.passphrase = '&§DG36';
@ -103,9 +122,24 @@ define(function(require) {
describe('confirm passphrase', function() {
var setStateStub;
it('should not continue if terms are not accepted', function(done) {
scope.state.passphrase = passphrase;
scope.state.confirmation = passphrase;
scope.state.agree = undefined;
scope.onError = function(err) {
expect(err.message).to.contain('Terms');
done();
};
scope.confirmPassphrase();
});
it('should unlock crypto', function(done) {
scope.state.passphrase = passphrase;
scope.state.confirmation = passphrase;
scope.state.agree = true;
emailDaoMock.unlock.withArgs({
passphrase: passphrase
}).yields();
@ -129,6 +163,8 @@ define(function(require) {
it('should not work when keypair generation fails', function(done) {
scope.state.passphrase = passphrase;
scope.state.confirmation = passphrase;
scope.state.agree = true;
emailDaoMock.unlock.withArgs({
passphrase: passphrase
}).yields(new Error('asd'));