[WO-462] Rework login workflow

* Make keygen and import possibilities clearer in login-initial
* Show spinner when generating key
* Use mobile design (wide buttons) everywhere
* Show info about key-sync in login-new-device (for mobile users)
* remove info popovers in login screens
* allow keyfile import even when keysync has been activated
This commit is contained in:
Tankred Hase 2014-07-31 19:08:21 +02:00
parent 9b618cc20f
commit e19d8a4e5b
8 changed files with 114 additions and 112 deletions

View File

@ -9,7 +9,8 @@ define(function(require) {
states = {
IDLE: 1,
PROCESSING: 2,
SET_PASSPHRASE: 2,
PROCESSING: 3,
DONE: 4
};
$scope.state.ui = states.IDLE; // initial state
@ -29,6 +30,17 @@ define(function(require) {
$location.path('/login-new-device');
};
$scope.setPassphrase = function() {
if (!$scope.state.agree) {
$scope.onError({
message: termsMsg
});
return;
}
$scope.setState(states.SET_PASSPHRASE);
};
/*
* Taken from jQuery validate.password plug-in 1.0
* http://bassistance.de/jquery-plugins/jquery-plugin-validate.password/
@ -90,20 +102,14 @@ define(function(require) {
return;
}
if (!$scope.state.agree) {
$scope.onError({
message: termsMsg
});
return;
}
$scope.setState(states.PROCESSING);
setTimeout(function() {
emailDao.unlock({
passphrase: (passphrase) ? passphrase : undefined
}, function(err) {
if (err) {
$scope.setState(states.IDLE);
$scope.setState(states.SET_PASSPHRASE);
$scope.onError(err);
return;
}

View File

@ -17,10 +17,14 @@
}
@include respond-to(desktop) {
margin: 135px auto 75px;
margin: 115px auto 75px;
}
}
.working {
text-align: center;
}
.spinner {
font-size: 150%;
}
@ -38,10 +42,7 @@
color: $btn-color;
margin-right: 10px;
margin-bottom: 10px;
@include respond-to(mobile) {
width: 100%;
}
width: 100%;
}
p, label {
@ -52,6 +53,18 @@
margin: 20px 0;
}
fieldset {
margin: 30px 0 40px;
legend {
color: $color-blue;
}
p {
margin: 0;
}
}
input {
margin-right: 10px;
}
@ -98,17 +111,6 @@
.popover-info {
display: none; // hide on mobile
}
@include respond-to(desktop) {
input[type="text"],
input[type="password"],
input[type="file"] {
width: auto;
}
.popover-info {
display: inline-block;
}
}
}
}
@ -122,19 +124,25 @@
.view-login-privatekey-download {
.content {
max-width: 500px;
input.code {
margin-right: 0;
width: auto;
fieldset {
margin-top: 20px;
}
.code {
max-width: 240px;
margin: 0 auto;
input {
margin-right: 0;
width: auto;
}
}
}
}
.view-login-set-credentials {
.content {
max-width: 450px;
b, a {
text-decoration: none;
}

View File

@ -9,7 +9,6 @@
<form>
<div>
<input class="input-text" type="password" ng-model="passphrase" ng-change="change()" ng-class="{'input-text-error':incorrect}" placeholder="Passphrase" tabindex="1" focus-me="true">
<span class="popover-info" data-icon-append="&#xe010;" popover="#passphrase-info"></span>
</div>
<a href="https://whiteout.io/revocation.html" title="Click here to reset your account." target="_blank">Forgot your passphrase?</a>
<div>
@ -17,15 +16,4 @@
</div>
</form>
</div><!--/content-->
</div>
<!-- popovers -->
<div id="passphrase-info" class="popover right" ng-controller="PopoverCtrl">
<div class="arrow"></div>
<div class="popover-title"><b>What is this?</b></div>
<div class="popover-content">
<p>A passphrase is like a password that protects your PGP key.</p>
<p>There is no way to access your messages without your passphrase.</p>
<p>If you have forgotten your passphrase, please request an account reset using the provided link. You will not be able to read previous messages after a reset.</p>
</div>
</div><!--/.popover-->
</div>

View File

@ -1,21 +1,14 @@
<div class="view-login view-login-initial" ng-class="{'waiting-cursor': state.ui === 2}">
<div class="view-login view-login-initial" ng-class="{'waiting-cursor': state.ui === 3}">
<div class="logo">
<img src="img/whiteout_logo.svg" alt="whiteout.io">
</div><!--/logo-->
<div class="content" ng-switch on="state.ui">
<div class="content">
<div ng-show="state.ui === 1">
<p><b>PGP key.</b> You can either import an existing PGP key or generate a new one. Your private key remains on your device and is not sent to our servers.</p>
<div ng-switch-when="1">
<p><b>PGP key.</b> You can either import an existing PGP key or generate a new one.</p>
<p>If you want to generate a new key, you can set a passphrase to protect your key on disk.</p>
<form>
<div>
<label class="input-error-message" ng-class="{'passphrase-label-ok': passphraseRating >= 2}">{{passphraseMsg}}</label><br>
<input class="input-text" type="password" ng-model="state.passphrase" ng-change="checkPassphraseQuality()" placeholder="Enter passphrase" tabindex="1" focus-me="true">
<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>
<input type="checkbox" ng-model="state.agree" name="checkbox" id="checkbox_id">
<label for="checkbox_id">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-service.html" target="_blank">Privacy Policy</a>.</label>
@ -23,24 +16,33 @@
<div>
<button wo-touch="importKey()" class="btn btn-alt">Import existing key</button>
<button type="submit" wo-touch="confirmPassphrase()" class="btn" ng-disabled="(state.confirmation || state.passphrase) && state.confirmation !== state.passphrase" tabindex="3">Generate new key</button>
<button type="submit" wo-touch="setPassphrase()" class="btn"tabindex="3">Generate new key</button>
</div>
</form>
</div>
<div ng-switch-when="2">
<div ng-show="state.ui === 2">
<p><b>Set passphrase.</b> You can set a passphrase to protect your key on disk. This must be entered everytime you start the app. For no passphrase just press continue.</p>
<form>
<div>
<label class="input-error-message" ng-class="{'passphrase-label-ok': passphraseRating >= 2}">{{passphraseMsg}}</label><br>
<input class="input-text" type="password" ng-model="state.passphrase" ng-change="checkPassphraseQuality()" placeholder="Enter passphrase" tabindex="1" focus-me="true">
<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">
</div>
<div>
<button type="submit" wo-touch="confirmPassphrase()" class="btn" ng-disabled="(state.confirmation || state.passphrase) && state.confirmation !== state.passphrase" tabindex="3">Continue</button>
</div>
</form>
</div>
<div ng-show="state.ui === 3">
<p><b>Generating key.</b> Please stand by. This can take a while...</p>
<div class="working">
<span class="spinner"></span>
</div><!--/.working-->
</div>
</div><!--/content-->
</div>
<!-- popovers -->
<div id="passphrase-info" class="popover right" ng-controller="PopoverCtrl">
<div class="arrow"></div>
<div class="popover-title"><b>What is this?</b></div>
<div class="popover-content">
<p>A passphrase is like a password that protects your PGP key.</p>
<p>If your device is lost or stolen the passphrase protects the contents of your mailbox.</p>
</div>
</div><!--/.popover-->
</div>

View File

@ -4,40 +4,22 @@
</div><!--/logo-->
<div class="content">
<p><b>Import keyfile.</b> To access your emails on this device, please import your existing key file.</p>
<p><b>Import PGP key.</b> Please import an existing key from the file system.</p>
<fieldset>
<legend>On a mobile device?</legend>
<p>If you cannot import your key via a USB stick, you can setup <i>Key sync</i> on a desktop PC to securely transfer your PGP key over the Whiteout cloud. <a href="https://blog.whiteout.io/2014/07/07/secure-pgp-key-sync-a-proposal/" target="_blank">Learn more</a>.</p>
</fieldset>
<form>
<div>
<input type="file" accept=".asc" file-reader tabindex="1">
<span class="popover-info" data-icon-append="&#xe010;" popover="#keyfile-info"></span>
</div>
<div>
<input class="input-text" type="password" ng-model="passphrase" ng-class="{'input-text-error':incorrect}" placeholder="Passphrase" tabindex="2" focus-me="true">
<span class="popover-info" data-icon-append="&#xe010;" popover="#passphrase-info"></span>
</div>
<a href="https://whiteout.io/revocation.html" title="Click here to reset your account." target="_blank">Lost your keyfile or passphrase?</a>
<div><button type="submit" wo-touch="confirmPassphrase()" class="btn" ng-disabled="!key" tabindex="3">Import</button>
</form>
</div>
</div>
<!-- popovers -->
<div id="keyfile-info" class="popover right" ng-controller="PopoverCtrl">
<div class="arrow"></div>
<div class="popover-title"><b>What is this?</b></div>
<div class="popover-content">
<p>The keyfile contains your PGP keys.</p>
<p>It can be exported on your first computer under "Account".</p>
<p>You can import it from a USB flash drive. Never send the keyfile to yourself via email.</p>
</div>
</div><!--/.popover-->
<div id="passphrase-info" class="popover right" ng-controller="PopoverCtrl">
<div class="arrow"></div>
<div class="popover-title"><b>What is this?</b></div>
<div class="popover-content">
<p>A passphrase is like a password that protects your PGP key.</p>
<p>There is no way to access your messages without your passphrase.</p>
<p>If you have forgotten your passphrase, please request an account reset using the provided link. You will not be able to read previous messages after a reset.</p>
</div>
</div><!--/.popover-->
</div>

View File

@ -7,10 +7,14 @@
<div class="step" ng-show="step === 1">
<p><b>Key sync.</b> We have sent you an email containing a recovery token. Please copy and paste the token below to download your key.</p>
<p>You can also just import the key file manually e.g. if you're on a device with USB access.</p>
<input type="text" class="input-text" size="42" ng-model="recoveryToken" placeholder="Recovery token" focus-me="step === 1">
<fieldset>
<legend>Got USB?</legend>
<p>You can also import the key file manually if you're on a device with USB access and your key is on a flash drive.</p>
</fieldset>
<div>
<a class="btn btn-alt" href="#login-new-device">Import key file</a>
<button class="btn" wo-touch="goForward()">Confirm token</button>
@ -20,12 +24,14 @@
<div class="step" ng-show="step === 2">
<p><b>Key sync.</b> Please enter the keychain code you wrote down during sync setup.</p>
<input type="text" class="input-text code" size="4" maxlength="4" ng-model="code0" focus-me="step === 2" focus-next ng-paste="handlePaste($event)"> -
<input type="text" class="input-text code" size="4" maxlength="4" ng-model="code1" focus-next> -
<input type="text" class="input-text code" size="4" maxlength="4" ng-model="code2" focus-next> -
<input type="text" class="input-text code" size="4" maxlength="4" ng-model="code3" focus-next> -
<input type="text" class="input-text code" size="4" maxlength="4" ng-model="code4" focus-next> -
<input type="text" class="input-text code" size="4" maxlength="4" ng-model="code5">
<div class="code">
<input type="text" class="input-text" size="4" maxlength="4" ng-model="code0" focus-me="step === 2" focus-next ng-paste="handlePaste($event)"> -
<input type="text" class="input-text" size="4" maxlength="4" ng-model="code1" focus-next> -
<input type="text" class="input-text" size="4" maxlength="4" ng-model="code2" focus-next> -
<input type="text" class="input-text" size="4" maxlength="4" ng-model="code3" focus-next> -
<input type="text" class="input-text" size="4" maxlength="4" ng-model="code4" focus-next> -
<input type="text" class="input-text" size="4" maxlength="4" ng-model="code5">
</div>
<!--<a href="https://whiteout.io/revocation.html" title="Click here to reset your account." target="_blank">Lost your keychain code?</a>-->
<div>

View File

@ -37,7 +37,7 @@
</div><!--/.settings-->
</div><!--/.details-->
<div ng-show="busy">
<div class="working" ng-show="busy">
<span class="spinner"></span>
</div>

View File

@ -123,12 +123,8 @@ define(function(require) {
});
});
describe('confirm passphrase', function() {
var setStateStub;
describe('setPassphrase', function() {
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) {
@ -136,13 +132,28 @@ define(function(require) {
done();
};
scope.confirmPassphrase();
scope.setPassphrase();
});
it('should continue', function(done) {
scope.state.agree = true;
var setStateStub = sinon.stub(scope, 'setState', function(state) {
expect(setStateStub.calledOnce).to.be.true;
expect(state).to.equal(2);
done();
});
scope.setPassphrase();
});
});
describe('confirm passphrase', function() {
var setStateStub;
it('should unlock crypto', function(done) {
scope.state.passphrase = passphrase;
scope.state.confirmation = passphrase;
scope.state.agree = true;
emailDaoMock.unlock.withArgs({
passphrase: passphrase
@ -168,7 +179,6 @@ 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
@ -176,9 +186,9 @@ define(function(require) {
setStateStub = sinon.stub(scope, 'setState', function(state) {
if (setStateStub.calledOnce) {
expect(state).to.equal(2);
expect(state).to.equal(3);
} else if (setStateStub.calledTwice) {
expect(state).to.equal(1);
expect(state).to.equal(2);
expect(emailDaoMock.unlock.calledOnce).to.be.true;
scope.setState.restore();
}