mirror of
https://github.com/moparisthebest/mail
synced 2024-11-25 18:32:20 -05:00
[WO-649] clean up login pages
* add spinners to all login pages * use inline error messages in all form instead of scope.onError * create newsletter service
This commit is contained in:
parent
082cbf192b
commit
cf1f60fbf9
@ -169,6 +169,8 @@ module.exports = function(grunt) {
|
||||
'test/unit/lawnchair-dao-test.js',
|
||||
'test/unit/keychain-dao-test.js',
|
||||
'test/unit/devicestorage-dao-test.js',
|
||||
'test/unit/newsletter-service-test.js',
|
||||
'test/unit/mail-config-service-test.js',
|
||||
'test/unit/dialog-ctrl-test.js',
|
||||
'test/unit/add-account-ctrl-test.js',
|
||||
'test/unit/create-account-ctrl-test.js',
|
||||
@ -192,7 +194,6 @@ module.exports = function(grunt) {
|
||||
'test/unit/invitation-dao-test.js',
|
||||
'test/unit/update-handler-test.js',
|
||||
'test/unit/connection-doctor-test.js',
|
||||
'test/unit/mail-config-service-test.js',
|
||||
'test/main.js'
|
||||
]
|
||||
},
|
||||
|
@ -37,6 +37,7 @@ var DialogCtrl = require('./controller/dialog'),
|
||||
errorUtil = require('./util/error'),
|
||||
backButtonUtil = require('./util/backbutton-handler');
|
||||
require('./directive/common'),
|
||||
require('./service/newsletter'),
|
||||
require('./service/mail-config');
|
||||
|
||||
// init main angular module including dependencies
|
||||
|
@ -10,21 +10,16 @@ var LoginExistingCtrl = function($scope, $location, $routeParams) {
|
||||
|
||||
var emailDao = appController._emailDao;
|
||||
|
||||
$scope.buttonEnabled = true;
|
||||
$scope.incorrect = false;
|
||||
|
||||
$scope.change = function() {
|
||||
$scope.incorrect = false;
|
||||
};
|
||||
|
||||
$scope.confirmPassphrase = function() {
|
||||
if (!$scope.passphrase) {
|
||||
if ($scope.form.$invalid) {
|
||||
$scope.errMsg = 'Please fill out all required fields!';
|
||||
return;
|
||||
}
|
||||
|
||||
// disable button once loggin has started
|
||||
$scope.buttonEnabled = false;
|
||||
$scope.busy = true;
|
||||
$scope.errMsg = undefined;
|
||||
$scope.incorrect = false;
|
||||
|
||||
unlockCrypto();
|
||||
};
|
||||
|
||||
@ -32,7 +27,7 @@ var LoginExistingCtrl = function($scope, $location, $routeParams) {
|
||||
var userId = emailDao._account.emailAddress;
|
||||
emailDao._keychain.getUserKeyPair(userId, function(err, keypair) {
|
||||
if (err) {
|
||||
handleError(err);
|
||||
displayError(err);
|
||||
return;
|
||||
}
|
||||
|
||||
@ -45,15 +40,14 @@ var LoginExistingCtrl = function($scope, $location, $routeParams) {
|
||||
|
||||
function onUnlock(err) {
|
||||
if (err) {
|
||||
$scope.incorrect = true;
|
||||
$scope.buttonEnabled = true;
|
||||
$scope.$apply();
|
||||
displayError(err);
|
||||
return;
|
||||
}
|
||||
|
||||
appController._auth.storeCredentials(function(err) {
|
||||
if (err) {
|
||||
return $scope.onError(err);
|
||||
displayError(err);
|
||||
return;
|
||||
}
|
||||
|
||||
$location.path('/desktop');
|
||||
@ -61,10 +55,11 @@ var LoginExistingCtrl = function($scope, $location, $routeParams) {
|
||||
});
|
||||
}
|
||||
|
||||
function handleError(err) {
|
||||
function displayError(err) {
|
||||
$scope.busy = false;
|
||||
$scope.incorrect = true;
|
||||
$scope.buttonEnabled = true;
|
||||
$scope.onError(err);
|
||||
$scope.errMsg = err.errMsg || err.message;
|
||||
$scope.$apply();
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -2,20 +2,24 @@
|
||||
|
||||
var appController = require('../app-controller');
|
||||
|
||||
var LoginInitialCtrl = function($scope, $location, $routeParams) {
|
||||
var LoginInitialCtrl = function($scope, $location, $routeParams, newsletter) {
|
||||
if (!appController._emailDao && !$routeParams.dev) {
|
||||
$location.path('/'); // init app
|
||||
return;
|
||||
}
|
||||
|
||||
var emailDao = appController._emailDao,
|
||||
states, termsMsg = 'You must accept the Terms of Service to continue.';
|
||||
if (appController._emailDao) {
|
||||
var emailDao = appController._emailDao,
|
||||
emailAddress = emailDao._account.emailAddress;
|
||||
}
|
||||
|
||||
var termsMsg = 'You must accept the Terms of Service to continue.',
|
||||
states = {
|
||||
IDLE: 1,
|
||||
PROCESSING: 2,
|
||||
DONE: 3
|
||||
};
|
||||
|
||||
states = {
|
||||
IDLE: 1,
|
||||
PROCESSING: 2,
|
||||
DONE: 3
|
||||
};
|
||||
$scope.state.ui = states.IDLE; // initial state
|
||||
|
||||
//
|
||||
@ -26,15 +30,15 @@ var LoginInitialCtrl = function($scope, $location, $routeParams) {
|
||||
* Continue to key import screen
|
||||
*/
|
||||
$scope.importKey = function() {
|
||||
if (!$scope.state.agree) {
|
||||
$scope.onError({
|
||||
message: termsMsg
|
||||
});
|
||||
if (!$scope.agree) {
|
||||
displayError(new Error(termsMsg));
|
||||
return;
|
||||
}
|
||||
|
||||
$scope.errMsg = undefined;
|
||||
|
||||
// sing up to newsletter
|
||||
$scope.signUpToNewsletter();
|
||||
newsletter.signup(emailAddress, $scope.newsletter);
|
||||
// go to key import
|
||||
$location.path('/login-new-device');
|
||||
};
|
||||
@ -43,77 +47,47 @@ var LoginInitialCtrl = function($scope, $location, $routeParams) {
|
||||
* Continue to keygen
|
||||
*/
|
||||
$scope.generateKey = function() {
|
||||
if (!$scope.state.agree) {
|
||||
$scope.onError({
|
||||
message: termsMsg
|
||||
});
|
||||
if (!$scope.agree) {
|
||||
displayError(new Error(termsMsg));
|
||||
return;
|
||||
}
|
||||
|
||||
$scope.errMsg = undefined;
|
||||
|
||||
// sing up to newsletter
|
||||
$scope.signUpToNewsletter();
|
||||
newsletter.signup(emailAddress, $scope.newsletter);
|
||||
// go to set keygen screen
|
||||
$scope.setState(states.PROCESSING);
|
||||
|
||||
setTimeout(function() {
|
||||
emailDao.unlock({
|
||||
passphrase: undefined // generate key without passphrase
|
||||
}, function(err) {
|
||||
emailDao.unlock({
|
||||
passphrase: undefined // generate key without passphrase
|
||||
}, function(err) {
|
||||
if (err) {
|
||||
displayError(err);
|
||||
return;
|
||||
}
|
||||
|
||||
appController._auth.storeCredentials(function(err) {
|
||||
if (err) {
|
||||
$scope.setState(states.IDLE);
|
||||
$scope.onError(err);
|
||||
displayError(err);
|
||||
return;
|
||||
}
|
||||
|
||||
appController._auth.storeCredentials(function(err) {
|
||||
if (err) {
|
||||
return $scope.onError(err);
|
||||
}
|
||||
|
||||
$location.path('/desktop');
|
||||
$scope.$apply();
|
||||
});
|
||||
$location.path('/desktop');
|
||||
$scope.$apply();
|
||||
});
|
||||
}, 500);
|
||||
};
|
||||
|
||||
/**
|
||||
* [signUpToNewsletter description]
|
||||
* @param {Function} callback (optional)
|
||||
*/
|
||||
$scope.signUpToNewsletter = function(callback) {
|
||||
if (!$scope.state.newsletter) {
|
||||
return;
|
||||
}
|
||||
|
||||
var address = emailDao._account.emailAddress;
|
||||
var uri = 'https://whiteout.us8.list-manage.com/subscribe/post?u=52ea5a9e1be9e1d194f184158&id=6538e8f09f';
|
||||
|
||||
var formData = new FormData();
|
||||
formData.append('EMAIL', address);
|
||||
formData.append('b_52ea5a9e1be9e1d194f184158_6538e8f09f', '');
|
||||
|
||||
var xhr = new XMLHttpRequest();
|
||||
xhr.open('post', uri, true);
|
||||
|
||||
xhr.onload = function() {
|
||||
if (callback) {
|
||||
callback(null, xhr);
|
||||
}
|
||||
};
|
||||
|
||||
xhr.onerror = function(err) {
|
||||
if (callback) {
|
||||
callback(err);
|
||||
}
|
||||
};
|
||||
|
||||
xhr.send(formData);
|
||||
});
|
||||
};
|
||||
|
||||
$scope.setState = function(state) {
|
||||
$scope.state.ui = state;
|
||||
};
|
||||
|
||||
function displayError(err) {
|
||||
$scope.setState(states.IDLE);
|
||||
$scope.errMsg = err.errMsg || err.message;
|
||||
$scope.$apply();
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = LoginInitialCtrl;
|
@ -14,7 +14,15 @@ var LoginExistingCtrl = function($scope, $location, $routeParams) {
|
||||
$scope.incorrect = false;
|
||||
|
||||
$scope.confirmPassphrase = function() {
|
||||
if ($scope.form.$invalid || !$scope.key) {
|
||||
$scope.errMsg = 'Please fill out all required fields!';
|
||||
return;
|
||||
}
|
||||
|
||||
$scope.busy = true;
|
||||
$scope.errMsg = undefined; // reset error msg
|
||||
$scope.incorrect = false;
|
||||
|
||||
unlockCrypto();
|
||||
};
|
||||
|
||||
@ -23,7 +31,7 @@ var LoginExistingCtrl = function($scope, $location, $routeParams) {
|
||||
// check if user already has a public key on the key server
|
||||
emailDao._keychain.getUserKeyPair(userId, function(err, keypair) {
|
||||
if (err) {
|
||||
$scope.onError(err);
|
||||
$scope.displayError(err);
|
||||
return;
|
||||
}
|
||||
|
||||
@ -34,7 +42,7 @@ var LoginExistingCtrl = function($scope, $location, $routeParams) {
|
||||
try {
|
||||
$scope.key.publicKeyArmored = pgp.extractPublicKey($scope.key.privateKeyArmored);
|
||||
} catch (e) {
|
||||
$scope.onError(new Error('Error parsing public key from private key!'));
|
||||
$scope.displayError(new Error('Error reading PGP key!'));
|
||||
return;
|
||||
}
|
||||
}
|
||||
@ -45,7 +53,7 @@ var LoginExistingCtrl = function($scope, $location, $routeParams) {
|
||||
privKeyParams = pgp.getKeyParams($scope.key.privateKeyArmored);
|
||||
pubKeyParams = pgp.getKeyParams($scope.key.publicKeyArmored);
|
||||
} catch (e) {
|
||||
$scope.onError(new Error('Error reading key params!'));
|
||||
$scope.displayError(new Error('Error reading key params!'));
|
||||
return;
|
||||
}
|
||||
|
||||
@ -74,7 +82,7 @@ var LoginExistingCtrl = function($scope, $location, $routeParams) {
|
||||
}, function(err) {
|
||||
if (err) {
|
||||
$scope.incorrect = true;
|
||||
$scope.onError(err);
|
||||
$scope.displayError(err);
|
||||
return;
|
||||
}
|
||||
|
||||
@ -85,19 +93,26 @@ var LoginExistingCtrl = function($scope, $location, $routeParams) {
|
||||
|
||||
function onUnlock(err) {
|
||||
if (err) {
|
||||
$scope.onError(err);
|
||||
$scope.displayError(err);
|
||||
return;
|
||||
}
|
||||
|
||||
appController._auth.storeCredentials(function(err) {
|
||||
if (err) {
|
||||
return $scope.onError(err);
|
||||
$scope.displayError(err);
|
||||
return;
|
||||
}
|
||||
|
||||
$location.path('/desktop');
|
||||
$scope.$apply();
|
||||
});
|
||||
}
|
||||
|
||||
$scope.displayError = function(err) {
|
||||
$scope.busy = false;
|
||||
$scope.errMsg = err.errMsg || err.message;
|
||||
$scope.$apply();
|
||||
};
|
||||
};
|
||||
|
||||
var ngModule = angular.module('login-new-device', []);
|
||||
@ -117,7 +132,7 @@ ngModule.directive('fileReader', function() {
|
||||
keyParts;
|
||||
|
||||
if (index === -1) {
|
||||
scope.onError(new Error('Error parsing private PGP key block!'));
|
||||
scope.displayError(new Error('Error parsing private PGP key block!'));
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -8,12 +8,116 @@ var LoginPrivateKeyDownloadCtrl = function($scope, $location, $routeParams) {
|
||||
return;
|
||||
}
|
||||
|
||||
var keychain = appController._keychain,
|
||||
emailDao = appController._emailDao,
|
||||
userId = emailDao._account.emailAddress;
|
||||
if (appController._emailDao) {
|
||||
var keychain = appController._keychain,
|
||||
emailDao = appController._emailDao,
|
||||
userId = emailDao._account.emailAddress;
|
||||
}
|
||||
|
||||
$scope.step = 1;
|
||||
|
||||
//
|
||||
// Token
|
||||
//
|
||||
|
||||
$scope.checkToken = function() {
|
||||
if ($scope.tokenForm.$invalid) {
|
||||
$scope.errMsg = 'Please enter a valid recovery token!';
|
||||
return;
|
||||
}
|
||||
|
||||
$scope.busy = true;
|
||||
$scope.errMsg = undefined;
|
||||
|
||||
$scope.verifyRecoveryToken(function() {
|
||||
$scope.busy = false;
|
||||
$scope.errMsg = undefined;
|
||||
$scope.step++;
|
||||
$scope.$apply();
|
||||
});
|
||||
};
|
||||
|
||||
$scope.verifyRecoveryToken = function(callback) {
|
||||
keychain.getUserKeyPair(userId, function(err, keypair) {
|
||||
if (err) {
|
||||
displayError(err);
|
||||
return;
|
||||
}
|
||||
|
||||
// remember for storage later
|
||||
$scope.cachedKeypair = keypair;
|
||||
|
||||
keychain.downloadPrivateKey({
|
||||
userId: userId,
|
||||
keyId: keypair.publicKey._id,
|
||||
recoveryToken: $scope.recoveryToken.toUpperCase()
|
||||
}, function(err, encryptedPrivateKey) {
|
||||
if (err) {
|
||||
displayError(err);
|
||||
return;
|
||||
}
|
||||
|
||||
$scope.encryptedPrivateKey = encryptedPrivateKey;
|
||||
callback();
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
//
|
||||
// Keychain code
|
||||
//
|
||||
|
||||
$scope.checkCode = function() {
|
||||
if ($scope.codeForm.$invalid) {
|
||||
$scope.errMsg = 'Please fill out all required fields!';
|
||||
return;
|
||||
}
|
||||
|
||||
$scope.busy = true;
|
||||
$scope.errMsg = undefined;
|
||||
|
||||
$scope.decryptAndStorePrivateKeyLocally();
|
||||
};
|
||||
|
||||
$scope.decryptAndStorePrivateKeyLocally = function() {
|
||||
var inputCode = '' + $scope.code0 + $scope.code1 + $scope.code2 + $scope.code3 + $scope.code4 + $scope.code5;
|
||||
|
||||
var options = $scope.encryptedPrivateKey;
|
||||
options.code = inputCode.toUpperCase();
|
||||
|
||||
keychain.decryptAndStorePrivateKeyLocally(options, function(err, privateKey) {
|
||||
if (err) {
|
||||
displayError(err);
|
||||
return;
|
||||
}
|
||||
|
||||
// add private key to cached keypair object
|
||||
$scope.cachedKeypair.privateKey = privateKey;
|
||||
|
||||
// try empty passphrase
|
||||
emailDao.unlock({
|
||||
keypair: $scope.cachedKeypair,
|
||||
passphrase: undefined
|
||||
}, function(err) {
|
||||
if (err) {
|
||||
// go to passphrase login screen
|
||||
$scope.goTo('/login-existing');
|
||||
return;
|
||||
}
|
||||
|
||||
// passphrase is corrent ... go to main app
|
||||
appController._auth.storeCredentials(function(err) {
|
||||
if (err) {
|
||||
displayError(err);
|
||||
return;
|
||||
}
|
||||
|
||||
$scope.goTo('/desktop');
|
||||
});
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
$scope.handlePaste = function(event) {
|
||||
var evt = event;
|
||||
if (evt.originalEvent) {
|
||||
@ -34,99 +138,21 @@ var LoginPrivateKeyDownloadCtrl = function($scope, $location, $routeParams) {
|
||||
$scope.code5 = value.slice(20, 24);
|
||||
};
|
||||
|
||||
$scope.verifyRecoveryToken = function(callback) {
|
||||
if (!$scope.recoveryToken) {
|
||||
$scope.onError(new Error('Please set the recovery token!'));
|
||||
return;
|
||||
}
|
||||
|
||||
keychain.getUserKeyPair(userId, function(err, keypair) {
|
||||
if (err) {
|
||||
$scope.onError(err);
|
||||
return;
|
||||
}
|
||||
|
||||
// remember for storage later
|
||||
$scope.cachedKeypair = keypair;
|
||||
|
||||
keychain.downloadPrivateKey({
|
||||
userId: userId,
|
||||
keyId: keypair.publicKey._id,
|
||||
recoveryToken: $scope.recoveryToken.toUpperCase()
|
||||
}, function(err, encryptedPrivateKey) {
|
||||
if (err) {
|
||||
$scope.onError(err);
|
||||
return;
|
||||
}
|
||||
|
||||
$scope.encryptedPrivateKey = encryptedPrivateKey;
|
||||
callback();
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
$scope.decryptAndStorePrivateKeyLocally = function() {
|
||||
var inputCode = '' + $scope.code0 + $scope.code1 + $scope.code2 + $scope.code3 + $scope.code4 + $scope.code5;
|
||||
|
||||
if (!inputCode) {
|
||||
$scope.onError(new Error('Please enter the keychain code!'));
|
||||
return;
|
||||
}
|
||||
|
||||
var options = $scope.encryptedPrivateKey;
|
||||
options.code = inputCode.toUpperCase();
|
||||
|
||||
keychain.decryptAndStorePrivateKeyLocally(options, function(err, privateKey) {
|
||||
if (err) {
|
||||
$scope.onError(err);
|
||||
return;
|
||||
}
|
||||
|
||||
// add private key to cached keypair object
|
||||
$scope.cachedKeypair.privateKey = privateKey;
|
||||
|
||||
// try empty passphrase
|
||||
emailDao.unlock({
|
||||
keypair: $scope.cachedKeypair,
|
||||
passphrase: undefined
|
||||
}, function(err) {
|
||||
if (err) {
|
||||
// go to passphrase login screen
|
||||
$scope.goTo('/login-existing');
|
||||
return;
|
||||
}
|
||||
|
||||
// passphrase is corrent ... go to main app
|
||||
appController._auth.storeCredentials(function(err) {
|
||||
if (err) {
|
||||
return $scope.onError(err);
|
||||
}
|
||||
|
||||
$scope.goTo('/desktop');
|
||||
});
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
$scope.goForward = function() {
|
||||
if ($scope.step === 1) {
|
||||
$scope.verifyRecoveryToken(function() {
|
||||
$scope.step++;
|
||||
$scope.$apply();
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
if ($scope.step === 2) {
|
||||
$scope.decryptAndStorePrivateKeyLocally();
|
||||
return;
|
||||
}
|
||||
};
|
||||
//
|
||||
// helper functions
|
||||
//
|
||||
|
||||
$scope.goTo = function(location) {
|
||||
$location.path(location);
|
||||
$scope.$apply();
|
||||
};
|
||||
|
||||
function displayError(err) {
|
||||
$scope.busy = false;
|
||||
$scope.incorrect = true;
|
||||
$scope.errMsg = err.errMsg || err.message;
|
||||
$scope.$apply();
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = LoginPrivateKeyDownloadCtrl;
|
@ -1,6 +1,6 @@
|
||||
'use strict';
|
||||
|
||||
var ngModule = angular.module('woServices', []);
|
||||
var ngModule = angular.module('woServices');
|
||||
ngModule.service('mailConfig', MailConfig);
|
||||
|
||||
var cfg = require('../app-config').config;
|
||||
|
45
src/js/service/newsletter.js
Normal file
45
src/js/service/newsletter.js
Normal file
@ -0,0 +1,45 @@
|
||||
'use strict';
|
||||
|
||||
var ngModule = angular.module('woServices', []);
|
||||
ngModule.service('newsletter', Newsletter);
|
||||
|
||||
function Newsletter($q) {
|
||||
this._q = $q;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sign up to the whiteout newsletter
|
||||
*/
|
||||
Newsletter.prototype.signup = function(emailAddress, agree) {
|
||||
return this._q(function(resolve, reject) {
|
||||
// validate email address
|
||||
if (emailAddress.indexOf('@') < 0) {
|
||||
reject(new Error('Invalid email address!'));
|
||||
return;
|
||||
}
|
||||
|
||||
if (!agree) {
|
||||
// don't sign up if the user has not agreed
|
||||
resolve(false);
|
||||
return;
|
||||
}
|
||||
|
||||
var formData = new FormData();
|
||||
formData.append('EMAIL', emailAddress);
|
||||
formData.append('b_52ea5a9e1be9e1d194f184158_6538e8f09f', '');
|
||||
|
||||
var uri = 'https://whiteout.us8.list-manage.com/subscribe/post?u=52ea5a9e1be9e1d194f184158&id=6538e8f09f';
|
||||
var xhr = new XMLHttpRequest();
|
||||
xhr.open('post', uri, true);
|
||||
|
||||
xhr.onload = function() {
|
||||
resolve(xhr);
|
||||
};
|
||||
|
||||
xhr.onerror = function(err) {
|
||||
reject(err);
|
||||
};
|
||||
|
||||
xhr.send(formData);
|
||||
});
|
||||
};
|
@ -3,6 +3,8 @@
|
||||
.page {
|
||||
height: 100%;
|
||||
overflow-y: auto;
|
||||
// allow scrolling on iOS
|
||||
-webkit-overflow-scrolling: touch;
|
||||
|
||||
// disable text selection
|
||||
user-select: none;
|
||||
|
@ -6,17 +6,19 @@
|
||||
<main class="page__main">
|
||||
<h2 class="typo-title">Unlock mailbox</h2>
|
||||
<p class="typo-paragraph">Please enter your passphrase to unlock the mailbox.</p>
|
||||
<form class="form">
|
||||
<!-- TODO add error messages -->
|
||||
<!--<p class="form__error-message" ng-show="errMsg">{{errMsg}}</p>
|
||||
<p class="form__error-message" ng-show="form.$invalid">Please fill out all required fields!</p>-->
|
||||
<form class="form" name="form">
|
||||
<p class="form__error-message" ng-show="errMsg">{{errMsg}}</p>
|
||||
|
||||
<div class="form__row">
|
||||
<input class="input-text input-text--big" type="password" ng-model="passphrase" ng-change="change()"
|
||||
ng-class="{'input-text--error':incorrect}" placeholder="Passphrase" tabindex="1" focus-me="true">
|
||||
<input type="password" ng-model="passphrase"
|
||||
class="input-text input-text--big" ng-class="{'input-text--error':incorrect}"
|
||||
placeholder="Passphrase" tabindex="1" focus-me="true" required>
|
||||
</div>
|
||||
<div class="spinner-block" ng-show="busy">
|
||||
<span class="spinner spinner--big"></span>
|
||||
</div>
|
||||
<div class="form__row">
|
||||
<button class="btn btn--big" type="submit" wo-touch="confirmPassphrase()" ng-disabled="!buttonEnabled" tabindex="2">
|
||||
<button class="btn btn--big" type="submit" ng-click="confirmPassphrase()" tabindex="2">
|
||||
<svg role="presentation"><use xlink:href="#icon-decrypted" /></svg>
|
||||
Unlock
|
||||
</button>
|
||||
|
@ -12,13 +12,11 @@
|
||||
</p>
|
||||
|
||||
<form class="form" name="form">
|
||||
<!-- TODO: remove error dialog and use inline error messages -->
|
||||
<!--<p class="form__error-message" ng-show="errMsg">{{errMsg}}</p>
|
||||
<p class="form__error-message" ng-show="form.$invalid">Please fill out all required fields!</p>-->
|
||||
<p class="form__error-message" ng-show="errMsg">{{errMsg}}</p>
|
||||
<div class="form__row">
|
||||
<label class="input-checkbox">
|
||||
<span class="checkbox">
|
||||
<input type="checkbox" ng-model="state.agree">
|
||||
<input type="checkbox" ng-model="agree">
|
||||
<span class="input-checkbox__box"><svg role="presentation"><use xlink:href="#icon-check" /></svg></span>
|
||||
</span>
|
||||
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>
|
||||
@ -27,17 +25,17 @@
|
||||
<div class="form__row">
|
||||
<label class="input-checkbox">
|
||||
<span class="checkbox">
|
||||
<input type="checkbox" ng-model="state.newsletter">
|
||||
<input type="checkbox" ng-model="newsletter">
|
||||
<span class="input-checkbox__box"><svg role="presentation"><use xlink:href="#icon-check" /></svg></span>
|
||||
</span>
|
||||
Stay up to date on Whiteout Networks products and important announcements.
|
||||
</label>
|
||||
</div>
|
||||
<div class="form__row">
|
||||
<button type="submit" wo-touch="generateKey()" class="btn" tabindex="3">Generate new key</button>
|
||||
<button type="submit" ng-click="generateKey()" class="btn" tabindex="3">Generate new key</button>
|
||||
</div>
|
||||
<div class="form__row">
|
||||
<button type="button" wo-touch="importKey()" class="btn btn--secondary">Import existing key</button>
|
||||
<button type="button" ng-click="importKey()" class="btn btn--secondary">Import existing key</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
@ -47,7 +45,7 @@
|
||||
<p class="typo-paragraph">
|
||||
Please stand by. This can take a while…
|
||||
</p>
|
||||
<div class="u-text-center">
|
||||
<div class="spinner-block spinner-block--standalone">
|
||||
<span class="spinner spinner--big"></span>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -17,23 +17,19 @@
|
||||
</p>
|
||||
</fieldset>
|
||||
|
||||
<!-- TODO -->
|
||||
<!--<p class="form__error-message" ng-show="errMsg">{{errMsg}}</p>
|
||||
<p class="form__error-message" ng-show="form.$invalid">Please fill out all required fields!</p>-->
|
||||
|
||||
<p class="form__error-message" ng-show="errMsg">{{errMsg}}</p>
|
||||
<div class="form__row">
|
||||
<input class="input-file" type="file" accept=".asc" file-reader tabindex="1">
|
||||
<input class="input-file" type="file" accept=".asc" file-reader tabindex="1" required>
|
||||
</div>
|
||||
<div class="form__row">
|
||||
<input class="input-text" type="password" ng-model="passphrase"
|
||||
ng-class="{'input-text--error':incorrect}" placeholder="Passphrase" tabindex="2">
|
||||
</div>
|
||||
<!-- TODO -->
|
||||
<!--<div class="spinner-block" ng-show="busy">
|
||||
<div class="spinner-block" ng-show="busy">
|
||||
<span class="spinner spinner--big"></span>
|
||||
</div>-->
|
||||
</div>
|
||||
<div class="form__row">
|
||||
<button type="submit" wo-touch="confirmPassphrase()" class="btn" ng-disabled="!key" tabindex="3">Import</button>
|
||||
<button type="submit" ng-click="confirmPassphrase()" class="btn" tabindex="3">Import</button>
|
||||
</div>
|
||||
</form>
|
||||
<p class="typo-paragraph">
|
||||
|
@ -8,16 +8,18 @@
|
||||
<div ng-show="step === 1">
|
||||
<h2 class="typo-title">Key sync</h2>
|
||||
<p class="typo-paragraph">We have sent you an email containing a recovery token. Please copy and paste the token below to download your key.</p>
|
||||
<form class="form">
|
||||
<!-- TODO add error messages -->
|
||||
<!--<p class="form__error-message" ng-show="errMsg">{{errMsg}}</p>
|
||||
<p class="form__error-message" ng-show="form.$invalid">Please fill out all required fields!</p>-->
|
||||
<form class="form" name="tokenForm">
|
||||
<p class="form__error-message" ng-show="errMsg">{{errMsg}}</p>
|
||||
|
||||
<div class="form__row">
|
||||
<input type="text" class="input-text input-text--big" size="6" maxlength="6" ng-model="recoveryToken" placeholder="Token" focus-me="step === 1" pattern="([a-zA-Z0-9]*)">
|
||||
<input type="text" class="input-text input-text--big" ng-class="{'input-text--error':incorrect}"
|
||||
size="6" maxlength="6" ng-model="recoveryToken" placeholder="Token" focus-me="step === 1" pattern="([a-zA-Z0-9]*)" required>
|
||||
</div>
|
||||
<div class="spinner-block" ng-show="busy">
|
||||
<span class="spinner spinner--big"></span>
|
||||
</div>
|
||||
<div class="form__row">
|
||||
<button class="btn btn--big" wo-touch="goForward()">Confirm token</button>
|
||||
<button class="btn btn--big" type="submit" ng-click="checkToken()">Confirm token</button>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
@ -37,23 +39,24 @@
|
||||
<div ng-show="step === 2">
|
||||
<h2 class="typo-title">Key sync</h2>
|
||||
<p class="typo-paragraph">Please enter the keychain code you wrote down during sync setup.</p>
|
||||
<form class="form">
|
||||
<!-- TODO add error messages -->
|
||||
<!--<p class="form__error-message" ng-show="errMsg">{{errMsg}}</p>
|
||||
<p class="form__error-message" ng-show="form.$invalid">Please fill out all required fields!</p>-->
|
||||
<form class="form" name="codeForm">
|
||||
<p class="form__error-message" ng-show="errMsg">{{errMsg}}</p>
|
||||
|
||||
<div class="form__row">
|
||||
<div class="input-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">
|
||||
<input type="text" class="input-text" size="4" maxlength="4" ng-model="code0" focus-me="step === 2" focus-next required ng-paste="handlePaste($event)"> -
|
||||
<input type="text" class="input-text" size="4" maxlength="4" ng-model="code1" focus-next required> -
|
||||
<input type="text" class="input-text" size="4" maxlength="4" ng-model="code2" focus-next required> -
|
||||
<input type="text" class="input-text" size="4" maxlength="4" ng-model="code3" focus-next required> -
|
||||
<input type="text" class="input-text" size="4" maxlength="4" ng-model="code4" focus-next required> -
|
||||
<input type="text" class="input-text" size="4" maxlength="4" ng-model="code5" required>
|
||||
</div>
|
||||
</div>
|
||||
<div class="spinner-block" ng-show="busy">
|
||||
<span class="spinner spinner--big"></span>
|
||||
</div>
|
||||
<div class="form__row">
|
||||
<button class="btn" wo-touch="goForward()">Complete sync</button>
|
||||
<button class="btn" type="submit" ng-click="checkCode()">Complete sync</button>
|
||||
</div>
|
||||
</form>
|
||||
<p class="typo-paragraph">
|
||||
|
@ -108,7 +108,7 @@
|
||||
<span class="spinner spinner--big"></span>
|
||||
</div>
|
||||
<div class="form__row">
|
||||
<button type="submit" ng-disabled="form.$invalid" ng-click="test()" class="btn">Login</button>
|
||||
<button type="submit" ng-click="test()" class="btn">Login</button>
|
||||
</div>
|
||||
</form>
|
||||
</main>
|
||||
|
@ -12,10 +12,6 @@
|
||||
</p>
|
||||
|
||||
<form class="form" name="form">
|
||||
<! TODO use error messages -->
|
||||
<!--<p class="form__error-message" ng-show="errMsg">{{errMsg}}</p>
|
||||
<p class="form__error-message" ng-show="form.$invalid">Please fill out all required fields!</p>-->
|
||||
|
||||
<div class="form__row">
|
||||
<input class="input-text" type="password" ng-model="oldPassphrase" placeholder="Current passphrase" tabindex="1" focus-me="true">
|
||||
</div>
|
||||
|
@ -35,6 +35,7 @@ describe('Login (existing user) Controller unit test', function() {
|
||||
location = $location;
|
||||
scope = $rootScope.$new();
|
||||
scope.state = {};
|
||||
scope.form = {};
|
||||
ctrl = $controller(LoginExistingCtrl, {
|
||||
$scope: scope,
|
||||
$routeParams: {}
|
||||
@ -50,64 +51,37 @@ describe('Login (existing user) Controller unit test', function() {
|
||||
|
||||
describe('initial state', function() {
|
||||
it('should be well defined', function() {
|
||||
expect(scope.buttonEnabled).to.be.true;
|
||||
expect(scope.incorrect).to.be.false;
|
||||
expect(scope.change).to.exist;
|
||||
expect(scope.confirmPassphrase).to.exist;
|
||||
expect(scope.incorrect).to.be.undefined;
|
||||
});
|
||||
});
|
||||
|
||||
describe('functionality', function() {
|
||||
describe('change', function() {
|
||||
it('should set incorrect to false', function() {
|
||||
scope.incorrect = true;
|
||||
describe('confirm passphrase', function() {
|
||||
it('should unlock crypto and start', function() {
|
||||
var keypair = {},
|
||||
pathSpy = sinon.spy(location, 'path');
|
||||
scope.passphrase = passphrase;
|
||||
keychainMock.getUserKeyPair.withArgs(emailAddress).yields(null, keypair);
|
||||
emailDaoMock.unlock.withArgs({
|
||||
keypair: keypair,
|
||||
passphrase: passphrase
|
||||
}).yields();
|
||||
authMock.storeCredentials.yields();
|
||||
|
||||
scope.change();
|
||||
expect(scope.incorrect).to.be.false;
|
||||
});
|
||||
scope.confirmPassphrase();
|
||||
|
||||
expect(keychainMock.getUserKeyPair.calledOnce).to.be.true;
|
||||
expect(emailDaoMock.unlock.calledOnce).to.be.true;
|
||||
expect(pathSpy.calledOnce).to.be.true;
|
||||
expect(pathSpy.calledWith('/desktop')).to.be.true;
|
||||
});
|
||||
|
||||
describe('confirm passphrase', function() {
|
||||
it('should unlock crypto and start', function() {
|
||||
var keypair = {},
|
||||
pathSpy = sinon.spy(location, 'path');
|
||||
scope.passphrase = passphrase;
|
||||
keychainMock.getUserKeyPair.withArgs(emailAddress).yields(null, keypair);
|
||||
emailDaoMock.unlock.withArgs({
|
||||
keypair: keypair,
|
||||
passphrase: passphrase
|
||||
}).yields();
|
||||
authMock.storeCredentials.yields();
|
||||
it('should not work when keypair unavailable', function() {
|
||||
scope.passphrase = passphrase;
|
||||
keychainMock.getUserKeyPair.withArgs(emailAddress).yields(new Error('asd'));
|
||||
|
||||
|
||||
scope.confirmPassphrase();
|
||||
|
||||
expect(keychainMock.getUserKeyPair.calledOnce).to.be.true;
|
||||
expect(emailDaoMock.unlock.calledOnce).to.be.true;
|
||||
expect(pathSpy.calledOnce).to.be.true;
|
||||
expect(pathSpy.calledWith('/desktop')).to.be.true;
|
||||
});
|
||||
|
||||
it('should not do anything without passphrase', function() {
|
||||
var pathSpy = sinon.spy(location, 'path');
|
||||
scope.passphrase = '';
|
||||
|
||||
scope.confirmPassphrase();
|
||||
expect(pathSpy.callCount).to.equal(0);
|
||||
});
|
||||
|
||||
it('should not work when keypair unavailable', function(done) {
|
||||
scope.passphrase = passphrase;
|
||||
keychainMock.getUserKeyPair.withArgs(emailAddress).yields(new Error('asd'));
|
||||
|
||||
scope.onError = function(err) {
|
||||
expect(err.message).to.equal('asd');
|
||||
expect(keychainMock.getUserKeyPair.calledOnce).to.be.true;
|
||||
done();
|
||||
};
|
||||
|
||||
scope.confirmPassphrase();
|
||||
});
|
||||
scope.confirmPassphrase();
|
||||
expect(scope.errMsg).to.exist;
|
||||
expect(keychainMock.getUserKeyPair.calledOnce).to.be.true;
|
||||
});
|
||||
});
|
||||
});
|
@ -9,7 +9,7 @@ var Auth = require('../../src/js/bo/auth'),
|
||||
|
||||
describe('Login (initial user) Controller unit test', function() {
|
||||
var scope, ctrl, location, origEmailDao, emailDaoMock,
|
||||
origAuth, authMock,
|
||||
origAuth, authMock, newsletterStub,
|
||||
emailAddress = 'fred@foo.com',
|
||||
keyId, expectedKeyId,
|
||||
cryptoMock;
|
||||
@ -31,17 +31,19 @@ describe('Login (initial user) Controller unit test', function() {
|
||||
emailAddress: emailAddress,
|
||||
};
|
||||
|
||||
angular.module('logininitialtest', []);
|
||||
angular.module('logininitialtest', ['woServices']);
|
||||
mocks.module('logininitialtest');
|
||||
mocks.inject(function($rootScope, $controller, $location) {
|
||||
mocks.inject(function($rootScope, $controller, $location, newsletter) {
|
||||
scope = $rootScope.$new();
|
||||
location = $location;
|
||||
newsletterStub = sinon.stub(newsletter, 'signup');
|
||||
scope.state = {
|
||||
ui: {}
|
||||
};
|
||||
ctrl = $controller(LoginInitialCtrl, {
|
||||
$scope: scope,
|
||||
$routeParams: {}
|
||||
$routeParams: {},
|
||||
newsletter: newsletter
|
||||
});
|
||||
});
|
||||
});
|
||||
@ -58,140 +60,65 @@ describe('Login (initial user) Controller unit test', function() {
|
||||
});
|
||||
});
|
||||
|
||||
describe('signUpToNewsletter', function() {
|
||||
var xhrMock, requests;
|
||||
|
||||
beforeEach(function() {
|
||||
xhrMock = sinon.useFakeXMLHttpRequest();
|
||||
requests = [];
|
||||
|
||||
xhrMock.onCreate = function(xhr) {
|
||||
requests.push(xhr);
|
||||
};
|
||||
});
|
||||
|
||||
afterEach(function() {
|
||||
xhrMock.restore();
|
||||
});
|
||||
|
||||
it('should not signup', function() {
|
||||
scope.state.newsletter = false;
|
||||
|
||||
scope.signUpToNewsletter();
|
||||
expect(requests.length).to.equal(0);
|
||||
});
|
||||
|
||||
it('should fail', function(done) {
|
||||
scope.state.newsletter = true;
|
||||
|
||||
scope.signUpToNewsletter(function(err, xhr) {
|
||||
expect(err).to.exist;
|
||||
expect(xhr).to.not.exist;
|
||||
done();
|
||||
});
|
||||
|
||||
expect(requests.length).to.equal(1);
|
||||
requests[0].onerror('err');
|
||||
});
|
||||
|
||||
it('should work without callback', function() {
|
||||
scope.state.newsletter = true;
|
||||
|
||||
scope.signUpToNewsletter();
|
||||
|
||||
expect(requests.length).to.equal(1);
|
||||
requests[0].respond(200, {
|
||||
"Content-Type": "text/plain"
|
||||
}, 'foobar!');
|
||||
});
|
||||
});
|
||||
|
||||
describe('go to import key', function() {
|
||||
var signUpToNewsletterStub;
|
||||
beforeEach(function() {
|
||||
signUpToNewsletterStub = sinon.stub(scope, 'signUpToNewsletter');
|
||||
});
|
||||
afterEach(function() {
|
||||
signUpToNewsletterStub.restore();
|
||||
});
|
||||
|
||||
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');
|
||||
expect(signUpToNewsletterStub.called).to.be.false;
|
||||
done();
|
||||
};
|
||||
it('should not continue if terms are not accepted', function() {
|
||||
scope.agree = undefined;
|
||||
|
||||
scope.importKey();
|
||||
|
||||
expect(scope.errMsg).to.contain('Terms');
|
||||
expect(newsletterStub.called).to.be.false;
|
||||
});
|
||||
|
||||
it('should work', function() {
|
||||
scope.state.agree = true;
|
||||
scope.agree = true;
|
||||
scope.importKey();
|
||||
expect(signUpToNewsletterStub.calledOnce).to.be.true;
|
||||
expect(newsletterStub.calledOnce).to.be.true;
|
||||
expect(location.$$path).to.equal('/login-new-device');
|
||||
});
|
||||
});
|
||||
|
||||
describe('generate key', function() {
|
||||
var signUpToNewsletterStub;
|
||||
beforeEach(function() {
|
||||
signUpToNewsletterStub = sinon.stub(scope, 'signUpToNewsletter');
|
||||
});
|
||||
afterEach(function() {
|
||||
signUpToNewsletterStub.restore();
|
||||
});
|
||||
|
||||
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');
|
||||
expect(scope.state.ui).to.equal(1);
|
||||
expect(signUpToNewsletterStub.called).to.be.false;
|
||||
done();
|
||||
};
|
||||
it('should not continue if terms are not accepted', function() {
|
||||
scope.agree = undefined;
|
||||
|
||||
scope.generateKey();
|
||||
|
||||
expect(scope.errMsg).to.contain('Terms');
|
||||
expect(scope.state.ui).to.equal(1);
|
||||
expect(newsletterStub.called).to.be.false;
|
||||
});
|
||||
|
||||
it('should fail due to error in emailDao.unlock', function(done) {
|
||||
scope.state.agree = true;
|
||||
it('should fail due to error in emailDao.unlock', function() {
|
||||
scope.agree = true;
|
||||
|
||||
emailDaoMock.unlock.withArgs({
|
||||
passphrase: undefined
|
||||
}).yields(new Error());
|
||||
}).yields(new Error('asdf'));
|
||||
authMock.storeCredentials.yields();
|
||||
|
||||
scope.onError = function(err) {
|
||||
expect(err).to.exist;
|
||||
expect(scope.state.ui).to.equal(1);
|
||||
expect(signUpToNewsletterStub.called).to.be.true;
|
||||
done();
|
||||
};
|
||||
|
||||
scope.generateKey();
|
||||
expect(scope.state.ui).to.equal(2);
|
||||
|
||||
expect(scope.errMsg).to.exist;
|
||||
expect(scope.state.ui).to.equal(1);
|
||||
expect(newsletterStub.called).to.be.true;
|
||||
});
|
||||
|
||||
it('should unlock crypto', function(done) {
|
||||
scope.state.agree = true;
|
||||
it('should unlock crypto', function() {
|
||||
scope.agree = true;
|
||||
|
||||
emailDaoMock.unlock.withArgs({
|
||||
passphrase: undefined
|
||||
}).yields();
|
||||
authMock.storeCredentials.yields();
|
||||
|
||||
scope.$apply = function() {
|
||||
expect(scope.state.ui).to.equal(2);
|
||||
expect(location.$$path).to.equal('/desktop');
|
||||
expect(emailDaoMock.unlock.calledOnce).to.be.true;
|
||||
done();
|
||||
};
|
||||
|
||||
scope.generateKey();
|
||||
|
||||
expect(scope.errMsg).to.not.exist;
|
||||
expect(scope.state.ui).to.equal(2);
|
||||
expect(newsletterStub.called).to.be.true;
|
||||
expect(location.$$path).to.equal('/desktop');
|
||||
expect(emailDaoMock.unlock.calledOnce).to.be.true;
|
||||
});
|
||||
});
|
||||
});
|
@ -37,6 +37,7 @@ describe('Login (new device) Controller unit test', function() {
|
||||
scope.state = {
|
||||
ui: {}
|
||||
};
|
||||
scope.form = {};
|
||||
ctrl = $controller(LoginNewDeviceCtrl, {
|
||||
$scope: scope,
|
||||
$routeParams: {}
|
||||
@ -103,7 +104,7 @@ describe('Login (new device) Controller unit test', function() {
|
||||
expect(keychainMock.getUserKeyPair.calledOnce).to.be.true;
|
||||
});
|
||||
|
||||
it('should not work when keypair upload fails', function(done) {
|
||||
it('should not work when keypair upload fails', function() {
|
||||
scope.passphrase = passphrase;
|
||||
scope.key = {
|
||||
privateKeyArmored: 'b'
|
||||
@ -123,19 +124,15 @@ describe('Login (new device) Controller unit test', function() {
|
||||
errMsg: 'yo mamma.'
|
||||
});
|
||||
|
||||
scope.onError = function(err) {
|
||||
expect(err.errMsg).to.equal('yo mamma.');
|
||||
done();
|
||||
};
|
||||
|
||||
scope.confirmPassphrase();
|
||||
|
||||
expect(keychainMock.getUserKeyPair.calledOnce).to.be.true;
|
||||
expect(emailDaoMock.unlock.calledOnce).to.be.true;
|
||||
expect(keychainMock.putUserKeyPair.calledOnce).to.be.true;
|
||||
expect(scope.errMsg).to.equal('yo mamma.');
|
||||
});
|
||||
|
||||
it('should not work when unlock fails', function(done) {
|
||||
it('should not work when unlock fails', function() {
|
||||
scope.passphrase = passphrase;
|
||||
scope.key = {
|
||||
privateKeyArmored: 'b'
|
||||
@ -154,33 +151,28 @@ describe('Login (new device) Controller unit test', function() {
|
||||
errMsg: 'yo mamma.'
|
||||
});
|
||||
|
||||
scope.onError = function(err) {
|
||||
expect(err.errMsg).to.equal('yo mamma.');
|
||||
done();
|
||||
};
|
||||
|
||||
scope.confirmPassphrase();
|
||||
|
||||
expect(scope.incorrect).to.be.true;
|
||||
expect(keychainMock.getUserKeyPair.calledOnce).to.be.true;
|
||||
expect(emailDaoMock.unlock.calledOnce).to.be.true;
|
||||
expect(scope.errMsg).to.equal('yo mamma.');
|
||||
});
|
||||
|
||||
it('should not work when keypair retrieval', function(done) {
|
||||
it('should not work when keypair retrieval', function() {
|
||||
scope.passphrase = passphrase;
|
||||
scope.key = {
|
||||
privateKeyArmored: 'b'
|
||||
};
|
||||
|
||||
keychainMock.getUserKeyPair.withArgs(emailAddress).yields({
|
||||
errMsg: 'yo mamma.'
|
||||
});
|
||||
|
||||
scope.onError = function(err) {
|
||||
expect(err.errMsg).to.equal('yo mamma.');
|
||||
done();
|
||||
};
|
||||
|
||||
scope.confirmPassphrase();
|
||||
|
||||
expect(keychainMock.getUserKeyPair.calledOnce).to.be.true;
|
||||
expect(scope.errMsg).to.equal('yo mamma.');
|
||||
});
|
||||
});
|
||||
});
|
@ -33,6 +33,8 @@ describe('Login Private Key Download Controller unit test', function() {
|
||||
mocks.inject(function($controller, $rootScope) {
|
||||
scope = $rootScope.$new();
|
||||
scope.state = {};
|
||||
scope.tokenForm = {};
|
||||
scope.codeForm = {};
|
||||
ctrl = $controller(LoginPrivateKeyDownloadCtrl, {
|
||||
$location: location,
|
||||
$scope: scope,
|
||||
@ -55,6 +57,35 @@ describe('Login Private Key Download Controller unit test', function() {
|
||||
});
|
||||
});
|
||||
|
||||
describe('checkToken', function() {
|
||||
var verifyRecoveryTokenStub;
|
||||
|
||||
beforeEach(function() {
|
||||
verifyRecoveryTokenStub = sinon.stub(scope, 'verifyRecoveryToken');
|
||||
});
|
||||
afterEach(function() {
|
||||
verifyRecoveryTokenStub.restore();
|
||||
});
|
||||
|
||||
it('should fail for empty recovery token', function() {
|
||||
scope.tokenForm.$invalid = true;
|
||||
|
||||
scope.checkToken();
|
||||
|
||||
expect(verifyRecoveryTokenStub.calledOnce).to.be.false;
|
||||
expect(scope.errMsg).to.exist;
|
||||
});
|
||||
|
||||
it('should work', function() {
|
||||
verifyRecoveryTokenStub.yields();
|
||||
|
||||
scope.checkToken();
|
||||
|
||||
expect(verifyRecoveryTokenStub.calledOnce).to.be.true;
|
||||
expect(scope.step).to.equal(2);
|
||||
});
|
||||
});
|
||||
|
||||
describe('verifyRecoveryToken', function() {
|
||||
var testKeypair = {
|
||||
publicKey: {
|
||||
@ -62,53 +93,35 @@ describe('Login Private Key Download Controller unit test', function() {
|
||||
}
|
||||
};
|
||||
|
||||
it('should fail for empty recovery token', function(done) {
|
||||
scope.onError = function(err) {
|
||||
expect(err).to.exist;
|
||||
done();
|
||||
};
|
||||
it('should fail in keychain.getUserKeyPair', function() {
|
||||
keychainMock.getUserKeyPair.yields(new Error('asdf'));
|
||||
|
||||
scope.recoveryToken = undefined;
|
||||
scope.verifyRecoveryToken();
|
||||
|
||||
expect(scope.errMsg).to.exist;
|
||||
expect(keychainMock.getUserKeyPair.calledOnce).to.be.true;
|
||||
});
|
||||
|
||||
it('should fail in keychain.getUserKeyPair', function(done) {
|
||||
keychainMock.getUserKeyPair.yields(42);
|
||||
|
||||
scope.onError = function(err) {
|
||||
expect(err).to.exist;
|
||||
expect(keychainMock.getUserKeyPair.calledOnce).to.be.true;
|
||||
done();
|
||||
};
|
||||
|
||||
scope.recoveryToken = 'token';
|
||||
scope.verifyRecoveryToken();
|
||||
});
|
||||
|
||||
it('should fail in keychain.downloadPrivateKey', function(done) {
|
||||
it('should fail in keychain.downloadPrivateKey', function() {
|
||||
keychainMock.getUserKeyPair.yields(null, testKeypair);
|
||||
keychainMock.downloadPrivateKey.yields(42);
|
||||
|
||||
scope.onError = function(err) {
|
||||
expect(err).to.exist;
|
||||
expect(keychainMock.getUserKeyPair.calledOnce).to.be.true;
|
||||
expect(keychainMock.downloadPrivateKey.calledOnce).to.be.true;
|
||||
done();
|
||||
};
|
||||
|
||||
keychainMock.downloadPrivateKey.yields(new Error('asdf'));
|
||||
scope.recoveryToken = 'token';
|
||||
|
||||
scope.verifyRecoveryToken();
|
||||
|
||||
expect(scope.errMsg).to.exist;
|
||||
expect(keychainMock.getUserKeyPair.calledOnce).to.be.true;
|
||||
expect(keychainMock.downloadPrivateKey.calledOnce).to.be.true;
|
||||
});
|
||||
|
||||
it('should work', function(done) {
|
||||
it('should work', function() {
|
||||
keychainMock.getUserKeyPair.yields(null, testKeypair);
|
||||
keychainMock.downloadPrivateKey.yields(null, 'encryptedPrivateKey');
|
||||
|
||||
scope.recoveryToken = 'token';
|
||||
scope.verifyRecoveryToken(function() {
|
||||
expect(scope.encryptedPrivateKey).to.equal('encryptedPrivateKey');
|
||||
done();
|
||||
});
|
||||
|
||||
scope.verifyRecoveryToken(function() {});
|
||||
|
||||
expect(scope.encryptedPrivateKey).to.equal('encryptedPrivateKey');
|
||||
});
|
||||
});
|
||||
|
||||
@ -151,39 +164,20 @@ describe('Login Private Key Download Controller unit test', function() {
|
||||
};
|
||||
});
|
||||
|
||||
it('should fail on empty code', function(done) {
|
||||
scope.code0 = '';
|
||||
scope.code1 = '';
|
||||
scope.code2 = '';
|
||||
scope.code3 = '';
|
||||
scope.code4 = '';
|
||||
scope.code5 = '';
|
||||
|
||||
scope.onError = function(err) {
|
||||
expect(err).to.exist;
|
||||
done();
|
||||
};
|
||||
it('should fail on decryptAndStorePrivateKeyLocally', function() {
|
||||
keychainMock.decryptAndStorePrivateKeyLocally.yields(new Error('asdf'));
|
||||
|
||||
scope.decryptAndStorePrivateKeyLocally();
|
||||
});
|
||||
|
||||
it('should fail on decryptAndStorePrivateKeyLocally', function(done) {
|
||||
keychainMock.decryptAndStorePrivateKeyLocally.yields(42);
|
||||
|
||||
scope.onError = function(err) {
|
||||
expect(err).to.exist;
|
||||
expect(keychainMock.decryptAndStorePrivateKeyLocally.calledOnce).to.be.true;
|
||||
done();
|
||||
};
|
||||
|
||||
scope.decryptAndStorePrivateKeyLocally();
|
||||
expect(scope.errMsg).to.exist;
|
||||
expect(keychainMock.decryptAndStorePrivateKeyLocally.calledOnce).to.be.true;
|
||||
});
|
||||
|
||||
it('should goto /login-existing on emailDao.unlock fail', function(done) {
|
||||
keychainMock.decryptAndStorePrivateKeyLocally.yields(null, {
|
||||
encryptedKey: 'keyArmored'
|
||||
});
|
||||
emailDaoMock.unlock.yields(42);
|
||||
emailDaoMock.unlock.yields(new Error('asdf'));
|
||||
|
||||
scope.goTo = function(location) {
|
||||
expect(location).to.equal('/login-existing');
|
||||
@ -213,30 +207,6 @@ describe('Login Private Key Download Controller unit test', function() {
|
||||
});
|
||||
});
|
||||
|
||||
describe('goForward', function() {
|
||||
it('should work in step 1', function() {
|
||||
var verifyRecoveryTokenStub = sinon.stub(scope, 'verifyRecoveryToken');
|
||||
verifyRecoveryTokenStub.yields();
|
||||
scope.step = 1;
|
||||
|
||||
scope.goForward();
|
||||
|
||||
expect(verifyRecoveryTokenStub.calledOnce).to.be.true;
|
||||
expect(scope.step).to.equal(2);
|
||||
verifyRecoveryTokenStub.restore();
|
||||
});
|
||||
it('should work in step 2', function() {
|
||||
var decryptAndStorePrivateKeyLocallyStub = sinon.stub(scope, 'decryptAndStorePrivateKeyLocally');
|
||||
decryptAndStorePrivateKeyLocallyStub.returns();
|
||||
scope.step = 2;
|
||||
|
||||
scope.goForward();
|
||||
|
||||
expect(decryptAndStorePrivateKeyLocallyStub.calledOnce).to.be.true;
|
||||
decryptAndStorePrivateKeyLocallyStub.restore();
|
||||
});
|
||||
});
|
||||
|
||||
describe('goTo', function() {
|
||||
it('should work', function(done) {
|
||||
mocks.inject(function($controller, $rootScope, $location) {
|
||||
|
76
test/unit/newsletter-service-test.js
Normal file
76
test/unit/newsletter-service-test.js
Normal file
@ -0,0 +1,76 @@
|
||||
'use strict';
|
||||
|
||||
var mocks = angular.mock;
|
||||
require('../../src/js/service/newsletter');
|
||||
|
||||
describe('Newsletter Service unit test', function() {
|
||||
var newsletter;
|
||||
|
||||
beforeEach(function() {
|
||||
angular.module('newsletter-test', ['woServices']);
|
||||
mocks.module('newsletter-test');
|
||||
mocks.inject(function($injector) {
|
||||
newsletter = $injector.get('newsletter');
|
||||
});
|
||||
});
|
||||
|
||||
afterEach(function() {});
|
||||
|
||||
describe('signup', function() {
|
||||
var xhrMock, requests;
|
||||
|
||||
beforeEach(function() {
|
||||
xhrMock = sinon.useFakeXMLHttpRequest();
|
||||
requests = [];
|
||||
|
||||
xhrMock.onCreate = function(xhr) {
|
||||
requests.push(xhr);
|
||||
};
|
||||
});
|
||||
|
||||
afterEach(function() {
|
||||
xhrMock.restore();
|
||||
});
|
||||
|
||||
it('should not signup if user has not agreed', inject(function($rootScope) {
|
||||
newsletter.signup('text@example.com', false).then(function(result) {
|
||||
expect(result).to.be.false;
|
||||
});
|
||||
|
||||
$rootScope.$apply();
|
||||
expect(requests.length).to.equal(0);
|
||||
}));
|
||||
|
||||
it('should not signup due to invalid email address', inject(function($rootScope) {
|
||||
newsletter.signup('textexample.com', true).catch(function(err) {
|
||||
expect(err.message).to.contain('Invalid');
|
||||
});
|
||||
|
||||
$rootScope.$apply();
|
||||
expect(requests.length).to.equal(0);
|
||||
}));
|
||||
|
||||
it('should fail', inject(function($rootScope) {
|
||||
newsletter.signup('text@example.com', true).catch(function(err) {
|
||||
expect(err).to.exist;
|
||||
});
|
||||
|
||||
requests[0].onerror('err');
|
||||
$rootScope.$apply();
|
||||
expect(requests.length).to.equal(1);
|
||||
}));
|
||||
|
||||
it('should work', inject(function($rootScope) {
|
||||
newsletter.signup('text@example.com', true).then(function(result) {
|
||||
expect(result).to.exist;
|
||||
});
|
||||
|
||||
requests[0].respond(200, {
|
||||
"Content-Type": "text/plain"
|
||||
}, 'foobar!');
|
||||
$rootScope.$apply();
|
||||
expect(requests.length).to.equal(1);
|
||||
}));
|
||||
});
|
||||
|
||||
});
|
Loading…
Reference in New Issue
Block a user