mirror of
https://github.com/moparisthebest/mail
synced 2024-11-25 18:32:20 -05:00
[WO-233] Implement opt-in for terms of service
This commit is contained in:
parent
680ed6e0c3
commit
becff37b4b
10
README.md
10
README.md
@ -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:
|
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
|
* [OpenPGP.js](http://openpgpjs.org) (LGPL license): An implementation of OpenPGP in Javascript
|
||||||
* [Inbox](https://github.com/andris9/inbox): Simple IMAP client for node.js
|
* [Inbox](https://github.com/andris9/inbox) (MIT license): Simple IMAP client for node.js
|
||||||
* [Nodemailer](http://www.nodemailer.com): SMTP client for node.js
|
* [Nodemailer](http://www.nodemailer.com) (MIT license): SMTP client for node.js
|
||||||
* [Forge](https://github.com/digitalbazaar/forge): An implementation of TLS in Javascript
|
* [Forge](https://github.com/digitalbazaar/forge) (BSD license): 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
|
* [node-shims](https://github.com/whiteout-io/node-shims) (MIT license): Shims for wrapping node's net/tls (TCP socket) APIs around chrome.socket
|
||||||
|
@ -3,7 +3,7 @@ define(function(require) {
|
|||||||
|
|
||||||
var _ = require('underscore'),
|
var _ = require('underscore'),
|
||||||
app = {},
|
app = {},
|
||||||
cloudUrl, clientId;
|
appVersion, cloudUrl, clientId;
|
||||||
|
|
||||||
// parse manifest to get configurations for current runtime
|
// parse manifest to get configurations for current runtime
|
||||||
try {
|
try {
|
||||||
@ -16,6 +16,8 @@ define(function(require) {
|
|||||||
cloudUrl = cloudUrl.substring(0, cloudUrl.length - 1);
|
cloudUrl = cloudUrl.substring(0, cloudUrl.length - 1);
|
||||||
// get client ID for OAuth requests
|
// get client ID for OAuth requests
|
||||||
clientId = manifest.oauth2.client_id;
|
clientId = manifest.oauth2.client_id;
|
||||||
|
// get the app version
|
||||||
|
appVersion = manifest.version;
|
||||||
} catch (e) {}
|
} catch (e) {}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -44,7 +46,8 @@ define(function(require) {
|
|||||||
iconPath: '/img/icon.png',
|
iconPath: '/img/icon.png',
|
||||||
verificationUrl: '/verify/',
|
verificationUrl: '/verify/',
|
||||||
verificationUuidLength: 36,
|
verificationUuidLength: 36,
|
||||||
dbVersion: 1
|
dbVersion: 1,
|
||||||
|
appVersion: appVersion
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -9,6 +9,7 @@ requirejs([
|
|||||||
'js/controller/account',
|
'js/controller/account',
|
||||||
'js/controller/set-passphrase',
|
'js/controller/set-passphrase',
|
||||||
'js/controller/contacts',
|
'js/controller/contacts',
|
||||||
|
'js/controller/about',
|
||||||
'js/controller/login',
|
'js/controller/login',
|
||||||
'js/controller/login-initial',
|
'js/controller/login-initial',
|
||||||
'js/controller/login-new-device',
|
'js/controller/login-new-device',
|
||||||
@ -29,6 +30,7 @@ requirejs([
|
|||||||
AccountCtrl,
|
AccountCtrl,
|
||||||
SetPassphraseCtrl,
|
SetPassphraseCtrl,
|
||||||
ContactsCtrl,
|
ContactsCtrl,
|
||||||
|
AboutCtrl,
|
||||||
LoginCtrl,
|
LoginCtrl,
|
||||||
LoginInitialCtrl,
|
LoginInitialCtrl,
|
||||||
LoginNewDeviceCtrl,
|
LoginNewDeviceCtrl,
|
||||||
@ -96,6 +98,7 @@ requirejs([
|
|||||||
app.controller('AccountCtrl', AccountCtrl);
|
app.controller('AccountCtrl', AccountCtrl);
|
||||||
app.controller('SetPassphraseCtrl', SetPassphraseCtrl);
|
app.controller('SetPassphraseCtrl', SetPassphraseCtrl);
|
||||||
app.controller('ContactsCtrl', ContactsCtrl);
|
app.controller('ContactsCtrl', ContactsCtrl);
|
||||||
|
app.controller('AboutCtrl', AboutCtrl);
|
||||||
app.controller('DialogCtrl', DialogCtrl);
|
app.controller('DialogCtrl', DialogCtrl);
|
||||||
app.controller('PopoverCtrl', PopoverCtrl);
|
app.controller('PopoverCtrl', PopoverCtrl);
|
||||||
|
|
||||||
|
32
src/js/controller/about.js
Normal file
32
src/js/controller/about.js
Normal 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;
|
||||||
|
});
|
@ -6,7 +6,7 @@ define(function(require) {
|
|||||||
|
|
||||||
var LoginInitialCtrl = function($scope, $location) {
|
var LoginInitialCtrl = function($scope, $location) {
|
||||||
var emailDao = appController._emailDao,
|
var emailDao = appController._emailDao,
|
||||||
states;
|
states, termsMsg = 'You must accept the Terms of Service to continue.';
|
||||||
|
|
||||||
// global state... inherited to all child scopes
|
// global state... inherited to all child scopes
|
||||||
$scope.$root.state = {};
|
$scope.$root.state = {};
|
||||||
@ -25,6 +25,13 @@ define(function(require) {
|
|||||||
//
|
//
|
||||||
|
|
||||||
$scope.importKey = function() {
|
$scope.importKey = function() {
|
||||||
|
if (!$scope.state.agree) {
|
||||||
|
$scope.onError({
|
||||||
|
message: termsMsg
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
$location.path('/login-new-device');
|
$location.path('/login-new-device');
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -89,6 +96,13 @@ define(function(require) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!$scope.state.agree) {
|
||||||
|
$scope.onError({
|
||||||
|
message: termsMsg
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
$scope.setState(states.PROCESSING);
|
$scope.setState(states.PROCESSING);
|
||||||
setTimeout(function() {
|
setTimeout(function() {
|
||||||
emailDao.unlock({
|
emailDao.unlock({
|
||||||
|
@ -27,6 +27,7 @@
|
|||||||
@import "views/account";
|
@import "views/account";
|
||||||
@import "views/set-passphrase";
|
@import "views/set-passphrase";
|
||||||
@import "views/contacts";
|
@import "views/contacts";
|
||||||
|
@import "views/about";
|
||||||
@import "views/dialog";
|
@import "views/dialog";
|
||||||
@import "views/navigation";
|
@import "views/navigation";
|
||||||
@import "views/mail-list";
|
@import "views/mail-list";
|
||||||
|
@ -2,11 +2,6 @@
|
|||||||
padding: 0px;
|
padding: 0px;
|
||||||
color: $color-grey-dark;
|
color: $color-grey-dark;
|
||||||
|
|
||||||
@include respond-to(mobile) {
|
|
||||||
top: 0;
|
|
||||||
max-width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.control {
|
.control {
|
||||||
float: right;
|
float: right;
|
||||||
|
|
||||||
|
15
src/sass/views/_about.scss
Normal file
15
src/sass/views/_about.scss
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -1,7 +1,10 @@
|
|||||||
.view-dialog {
|
.view-dialog {
|
||||||
max-width: 350px;
|
|
||||||
height: auto;
|
height: auto;
|
||||||
top: 30%;
|
|
||||||
|
@include respond-to(desktop) {
|
||||||
|
max-width: 350px;
|
||||||
|
top: 30%;
|
||||||
|
}
|
||||||
|
|
||||||
p {
|
p {
|
||||||
text-align: center;
|
text-align: center;
|
||||||
|
@ -24,12 +24,12 @@
|
|||||||
margin-left: auto;
|
margin-left: auto;
|
||||||
margin-right: auto;
|
margin-right: auto;
|
||||||
|
|
||||||
|
b, a {
|
||||||
|
color: $color-blue;
|
||||||
|
}
|
||||||
|
|
||||||
p {
|
p {
|
||||||
line-height: 150%;
|
line-height: 150%;
|
||||||
|
|
||||||
b, a {
|
|
||||||
color: $color-blue;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
div {
|
div {
|
||||||
@ -69,6 +69,13 @@
|
|||||||
.passphrase-label-ok {
|
.passphrase-label-ok {
|
||||||
color: green;
|
color: green;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.opt-in-terms {
|
||||||
|
.checkbox-wrapper {
|
||||||
|
margin-top: 0;
|
||||||
|
float: left;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -76,7 +83,9 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.view-login-initial {
|
.view-login-initial {
|
||||||
button {
|
.content {
|
||||||
margin-right: 10px;
|
button {
|
||||||
|
margin-right: 10px;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
15
src/tpl/about.html
Normal file
15
src/tpl/about.html
Normal 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"></button>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<div class="content">
|
||||||
|
|
||||||
|
<img src="img/icon.png">
|
||||||
|
<h2>Whiteout Mail</h2>
|
||||||
|
<p>Version {{version}}</p>
|
||||||
|
<p>© {{ date | date:'yyyy'}} Whiteout Networks GmbH</p>
|
||||||
|
<p><a href="https://whiteout.io/imprint.html" target="_blank">Imprint</a> · <a href="https://whiteout.io/privacy.html" target="_blank">Privacy</a> · <a href="https://whiteout.io/terms.html" target="_blank">Terms</a> · <a href="https://github.com/whiteout-io/mail-html5#license" target="_blank">Licenses</a></p>
|
||||||
|
|
||||||
|
</div><!-- /.content -->
|
||||||
|
</div><!-- /.lightbox-body -->
|
@ -31,6 +31,9 @@
|
|||||||
<div class="lightbox-overlay" ng-class="{'show': state.contacts.open}">
|
<div class="lightbox-overlay" ng-class="{'show': state.contacts.open}">
|
||||||
<div class="lightbox lightbox-effect" ng-include="'tpl/contacts.html'"></div>
|
<div class="lightbox lightbox-effect" ng-include="'tpl/contacts.html'"></div>
|
||||||
</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-overlay" ng-class="{'show': state.dialog.open}">
|
||||||
<div class="lightbox lightbox-effect dialog view-dialog" ng-include="'tpl/dialog.html'"></div>
|
<div class="lightbox lightbox-effect dialog view-dialog" ng-include="'tpl/dialog.html'"></div>
|
||||||
</div>
|
</div>
|
@ -20,7 +20,7 @@
|
|||||||
|
|
||||||
<!-- lightbox -->
|
<!-- lightbox -->
|
||||||
<div class="lightbox-overlay" ng-class="{'show': state.dialog.open}">
|
<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>
|
</div>
|
||||||
|
|
||||||
<!-- popovers -->
|
<!-- popovers -->
|
||||||
|
@ -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">
|
<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="" popover="#passphrase-info"></span>
|
<span class="popover-info" data-icon-append="" popover="#passphrase-info"></span>
|
||||||
</div>
|
</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>
|
<div>
|
||||||
<button type="submit" ng-click="confirmPassphrase()" class="btn" ng-disabled="(state.confirmation || state.passphrase) && state.confirmation !== state.passphrase" tabindex="3">Continue</button>
|
<button type="submit" ng-click="confirmPassphrase()" class="btn" ng-disabled="(state.confirmation || state.passphrase) && state.confirmation !== state.passphrase" tabindex="3">Continue</button>
|
||||||
</div>
|
</div>
|
||||||
@ -29,7 +35,7 @@
|
|||||||
|
|
||||||
<!-- lightbox -->
|
<!-- lightbox -->
|
||||||
<div class="lightbox-overlay" ng-class="{'show': state.dialog.open}">
|
<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>
|
</div>
|
||||||
|
|
||||||
<!-- popovers -->
|
<!-- popovers -->
|
||||||
|
@ -22,7 +22,7 @@
|
|||||||
|
|
||||||
<!-- lightbox -->
|
<!-- lightbox -->
|
||||||
<div class="lightbox-overlay" ng-class="{'show': state.dialog.open}">
|
<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>
|
</div>
|
||||||
|
|
||||||
<!-- popovers -->
|
<!-- popovers -->
|
||||||
|
@ -12,5 +12,5 @@
|
|||||||
|
|
||||||
<!-- lightbox -->
|
<!-- lightbox -->
|
||||||
<div class="lightbox-overlay" ng-class="{'show': state.dialog.open}">
|
<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>
|
</div>
|
@ -17,7 +17,7 @@
|
|||||||
<ul class="nav-secondary">
|
<ul class="nav-secondary">
|
||||||
<li><a href="#" ng-click="state.account.toggle(true); $event.preventDefault()">Account</a></li>
|
<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="#" 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>
|
</ul>
|
||||||
|
|
||||||
<footer>
|
<footer>
|
||||||
|
@ -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() {
|
describe('check passphrase quality', function() {
|
||||||
it('should be too short', function() {
|
it('should be too short', function() {
|
||||||
scope.state.passphrase = '&§DG36';
|
scope.state.passphrase = '&§DG36';
|
||||||
@ -103,9 +122,24 @@ define(function(require) {
|
|||||||
describe('confirm passphrase', function() {
|
describe('confirm passphrase', function() {
|
||||||
var setStateStub;
|
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) {
|
it('should unlock crypto', function(done) {
|
||||||
scope.state.passphrase = passphrase;
|
scope.state.passphrase = passphrase;
|
||||||
scope.state.confirmation = passphrase;
|
scope.state.confirmation = passphrase;
|
||||||
|
scope.state.agree = true;
|
||||||
|
|
||||||
emailDaoMock.unlock.withArgs({
|
emailDaoMock.unlock.withArgs({
|
||||||
passphrase: passphrase
|
passphrase: passphrase
|
||||||
}).yields();
|
}).yields();
|
||||||
@ -129,6 +163,8 @@ define(function(require) {
|
|||||||
it('should not work when keypair generation fails', function(done) {
|
it('should not work when keypair generation fails', function(done) {
|
||||||
scope.state.passphrase = passphrase;
|
scope.state.passphrase = passphrase;
|
||||||
scope.state.confirmation = passphrase;
|
scope.state.confirmation = passphrase;
|
||||||
|
scope.state.agree = true;
|
||||||
|
|
||||||
emailDaoMock.unlock.withArgs({
|
emailDaoMock.unlock.withArgs({
|
||||||
passphrase: passphrase
|
passphrase: passphrase
|
||||||
}).yields(new Error('asd'));
|
}).yields(new Error('asd'));
|
||||||
|
Loading…
Reference in New Issue
Block a user