two factor page cleanup

This commit is contained in:
Kyle Spearrin 2017-06-26 22:24:10 -04:00
parent 64784d0e36
commit 1d102cad93
11 changed files with 197 additions and 64 deletions

View File

@ -131,10 +131,6 @@
"message": "Verification Code", "message": "Verification Code",
"description": "Verification Code" "description": "Verification Code"
}, },
"enterTwoStepVerCode": {
"message": "Enter your two-step verification code.",
"description": "Enter your two-step verification code."
},
"account": { "account": {
"message": "Account", "message": "Account",
"description": "Account" "description": "Account"

Binary file not shown.

After

Width:  |  Height:  |  Size: 174 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

View File

@ -13,11 +13,12 @@ var FolderRequest = function (folder) {
this.name = folder.name ? folder.name.encryptedString : null; this.name = folder.name ? folder.name.encryptedString : null;
}; };
var TokenRequest = function (email, masterPasswordHash, provider, token, device) { var TokenRequest = function (email, masterPasswordHash, provider, token, remember, device) {
this.email = email; this.email = email;
this.masterPasswordHash = masterPasswordHash; this.masterPasswordHash = masterPasswordHash;
this.token = token; this.token = token;
this.provider = provider; this.provider = provider;
this.remember = remember || remember !== false;
this.device = null; this.device = null;
if (device) { if (device) {
this.device = device; this.device = device;
@ -42,6 +43,7 @@ var TokenRequest = function (email, masterPasswordHash, provider, token, device)
if (this.token && this.provider != null && (typeof this.provider !== 'undefined')) { if (this.token && this.provider != null && (typeof this.provider !== 'undefined')) {
obj.twoFactorToken = this.token; obj.twoFactorToken = this.token;
obj.twoFactorProvider = this.provider; obj.twoFactorProvider = this.provider;
obj.twoFactorRemember = this.remember ? '1' : '0';
} }
return obj; return obj;
@ -60,6 +62,11 @@ var PasswordHintRequest = function (email) {
this.email = email; this.email = email;
}; };
var TwoFactorEmailRequest = function (email, masterPasswordHash) {
this.email = email;
this.masterPasswordHash = masterPasswordHash;
};
var DeviceTokenRequest = function () { var DeviceTokenRequest = function () {
this.pushToken = null; this.pushToken = null;
}; };

View File

@ -72,6 +72,7 @@ var IdentityTokenResponse = function (response) {
this.privateKey = response.PrivateKey; this.privateKey = response.PrivateKey;
this.key = response.Key; this.key = response.Key;
this.twoFactorToken = response.TwoFactorToken;
}; };
var ListResponse = function (data) { var ListResponse = function (data) {

View File

@ -1,7 +1,7 @@
angular angular
.module('bit.accounts') .module('bit.accounts')
.controller('accountsLoginTwoFactorController', function ($scope, $state, authService, toastr, utilsService, .controller('accountsLoginTwoFactorController', function ($scope, $state, authService, toastr, utilsService, SweetAlert,
$analytics, i18nService, $stateParams, $filter, constantsService, $timeout, $window, cryptoService, apiService) { $analytics, i18nService, $stateParams, $filter, constantsService, $timeout, $window, cryptoService, apiService) {
$scope.i18n = i18nService; $scope.i18n = i18nService;
utilsService.initListSectionItemListeners($(document), angular); utilsService.initListSectionItemListeners($(document), angular);
@ -11,11 +11,17 @@
var masterPassword = $stateParams.masterPassword; var masterPassword = $stateParams.masterPassword;
var providers = $stateParams.providers; var providers = $stateParams.providers;
if (!email || !masterPassword || !providers) {
$state.go('login');
return;
}
$scope.providerType = $stateParams.provider ? $stateParams.provider : getDefaultProvider(providers);
$scope.twoFactorEmail = null; $scope.twoFactorEmail = null;
$scope.token = null; $scope.token = null;
$scope.constantsProvider = constants.twoFactorProvider; $scope.constantsProvider = constants.twoFactorProvider;
$scope.providerType = $stateParams.provider ? $stateParams.provider : getDefaultProvider(providers);
$scope.u2fReady = false; $scope.u2fReady = false;
$scope.remember = { checked: false };
init(); init();
$scope.loginPromise = null; $scope.loginPromise = null;
@ -25,7 +31,12 @@
return; return;
} }
$scope.loginPromise = authService.logIn(email, masterPassword, $scope.providerType, token); if ($scope.providerType === constants.twoFactorProvider.email ||
$scope.providerType === constants.twoFactorProvider.authenticator) {
token = token.replace(' ', '')
}
$scope.loginPromise = authService.logIn(email, masterPassword, $scope.providerType, token, $scope.remember.checked);
$scope.loginPromise.then(function () { $scope.loginPromise.then(function () {
$analytics.eventTrack('Logged In From Two-step'); $analytics.eventTrack('Logged In From Two-step');
$state.go('tabs.vault', { animation: 'in-slide-left', syncOnLoad: true }); $state.go('tabs.vault', { animation: 'in-slide-left', syncOnLoad: true });
@ -43,17 +54,16 @@
} }
var key = cryptoService.makeKey(masterPassword, email); var key = cryptoService.makeKey(masterPassword, email);
var hash = cryptoService.hashPassword(masterPassword, key); cryptoService.hashPassword(masterPassword, key, function (hash) {
apiService.postTwoFactorEmail({ var request = new TwoFactorEmailRequest(email, hash);
email: email, apiService.postTwoFactorEmail(request, function () {
masterPasswordHash: hash
}, function () {
if (doToast) { if (doToast) {
toastr.success('Verification email sent to ' + $scope.twoFactorEmail + '.'); toastr.success('Verification email sent to ' + $scope.twoFactorEmail + '.');
} }
}, function () { }, function () {
toastr.error('Could not send verification email.'); toastr.error('Could not send verification email.');
}); });
});
}; };
$scope.anotherMethod = function () { $scope.anotherMethod = function () {
@ -131,7 +141,27 @@
else if ($scope.providerType === constants.twoFactorProvider.email) { else if ($scope.providerType === constants.twoFactorProvider.email) {
var params = providers[constants.twoFactorProvider.email]; var params = providers[constants.twoFactorProvider.email];
$scope.twoFactorEmail = params.Email; $scope.twoFactorEmail = params.Email;
if (Object.keys(providers).length > 1) {
if (chrome.extension.getViews({ type: 'popup' }).length > 0) {
SweetAlert.swal({
title: 'Two-step Login',
text: 'Clicking outside the popup window to check your email for your verification code will ' +
'cause this popup to close. ' +
'Do you want to open this popup in a new window so that it does not close?',
showCancelButton: true,
confirmButtonText: i18nService.yes,
cancelButtonText: i18nService.no
}, function (confirmed) {
if (confirmed) {
chrome.tabs.create({ url: '/popup/index.html#!/login' });
return;
}
else if (Object.keys(providers).length > 1) {
$scope.sendEmail(false);
}
});
}
else if (Object.keys(providers).length > 1) {
$scope.sendEmail(false); $scope.sendEmail(false);
} }
} }

View File

@ -11,6 +11,14 @@
<div class="title">{{i18n.verificationCode}}</div> <div class="title">{{i18n.verificationCode}}</div>
</div> </div>
<div class="content"> <div class="content">
<div class="two-factor-key-page">
<p ng-if="providerType === constantsProvider.authenticator">
Enter the 6 digit verification code from your authenticator app.
</p>
<p ng-if="providerType === constantsProvider.email">
Enter the 6 digit verification code that was emailed to <b>{{twoFactorEmail}}</b>.
</p>
</div>
<div class="list"> <div class="list">
<div class="list-section"> <div class="list-section">
<div class="list-section-items"> <div class="list-section-items">
@ -21,14 +29,14 @@
</div> </div>
<div class="list-section-item list-section-item-checkbox"> <div class="list-section-item list-section-item-checkbox">
<label for="remember">Remember me</label> <label for="remember">Remember me</label>
<input id="remember" name="Remember" type="checkbox" ng-model="remember"> <input id="remember" name="Remember" type="checkbox" ng-model="remember.checked">
</div>
</div>
<div class="list-section-footer">
{{i18n.enterTwoStepVerCode}}
</div> </div>
</div> </div>
</div> </div>
</div>
<p class="text-center text-accent" ng-if="providerType === constantsProvider.email">
<a href="#" stop-click ng-click="sendEmail(true)">Send verification code email again</a>
</p>
<p class="text-center text-accent"> <p class="text-center text-accent">
<a href="#" stop-click ng-click="anotherMethod()">Use another two-step login method</a> <a href="#" stop-click ng-click="anotherMethod()">Use another two-step login method</a>
</p> </p>
@ -61,6 +69,10 @@
<div class="title">YubiKey</div> <div class="title">YubiKey</div>
</div> </div>
<div class="content"> <div class="content">
<div class="two-factor-key-page">
<p>Insert your YubiKey into your computer's USB port, then touch its button.</p>
<img src="../images/two-factor/yubikey.jpg" alt="" class="img-rounded img-responsive" />
</div>
<div class="list"> <div class="list">
<div class="list-section"> <div class="list-section">
<div class="list-section-items"> <div class="list-section-items">
@ -71,12 +83,9 @@
</div> </div>
<div class="list-section-item list-section-item-checkbox"> <div class="list-section-item list-section-item-checkbox">
<label for="remember">Remember me</label> <label for="remember">Remember me</label>
<input id="remember" name="Remember" type="checkbox" ng-model="remember"> <input id="remember" name="Remember" type="checkbox" ng-model="remember.checked">
</div> </div>
</div> </div>
<div class="list-section-footer">
Touch the YubiKey button.
</div>
</div> </div>
</div> </div>
<p class="text-center text-accent"> <p class="text-center text-accent">
@ -96,10 +105,27 @@
<div class="title">FIDO U2F</div> <div class="title">FIDO U2F</div>
</div> </div>
<div class="content"> <div class="content">
<div ng-if="!u2fReady">Loading...</div> <div class="two-factor-key-page">
<div ng-if="u2fReady">Touch button</div>
<iframe id="u2f_iframe" class="hide"></iframe> <iframe id="u2f_iframe" class="hide"></iframe>
<p class="text-center text-accent"> <p ng-if="!u2fReady">Loading...</p>
<div ng-if="u2fReady">
<p>
Insert your Security Key into your computer's USB port. If it has a button, touch it.
</p>
<img src="../images/two-factor/u2fkey.jpg" alt="" class="img-rounded img-responsive" />
</div>
</div>
<div class="list">
<div class="list-section">
<div class="list-section-items">
<div class="list-section-item list-section-item-checkbox">
<label for="remember">Remember me</label>
<input id="remember" name="Remember" type="checkbox" ng-model="remember.checked">
</div>
</div>
</div>
</div>
<p class="text-accent text-center">
<a href="#" stop-click ng-click="anotherMethod()">Use another two-step login method</a> <a href="#" stop-click ng-click="anotherMethod()">Use another two-step login method</a>
</p> </p>
</div> </div>

View File

@ -2,17 +2,30 @@
.module('bit.services') .module('bit.services')
.factory('authService', function (cryptoService, apiService, userService, tokenService, $q, $rootScope, loginService, .factory('authService', function (cryptoService, apiService, userService, tokenService, $q, $rootScope, loginService,
folderService, settingsService, syncService, appIdService, utilsService) { folderService, settingsService, syncService, appIdService, utilsService, constantsService) {
var _service = {}; var _service = {};
_service.logIn = function (email, masterPassword, twoFactorProvider, twoFactorToken) { _service.logIn = function (email, masterPassword, twoFactorProvider, twoFactorToken, remember) {
email = email.toLowerCase(); email = email.toLowerCase();
var key = cryptoService.makeKey(masterPassword, email); var key = cryptoService.makeKey(masterPassword, email);
var deferred = $q.defer(); var deferred = $q.defer();
cryptoService.hashPassword(masterPassword, key, function (hashedPassword) { cryptoService.hashPassword(masterPassword, key, function (hashedPassword) {
appIdService.getAppId(function (appId) { appIdService.getAppId(function (appId) {
tokenService.getTwoFactorToken(email, function (twoFactorRememberedToken) {
var deviceRequest = new DeviceRequest(appId, utilsService); var deviceRequest = new DeviceRequest(appId, utilsService);
var request = new TokenRequest(email, hashedPassword, twoFactorProvider, twoFactorToken, deviceRequest); var request;
if (twoFactorToken && typeof (twoFactorProvider) !== 'undefined' && twoFactorProvider !== null) {
request = new TokenRequest(email, hashedPassword, twoFactorProvider, twoFactorToken, remember,
deviceRequest);
}
else if (twoFactorRememberedToken) {
request = new TokenRequest(email, hashedPassword, constantsService.twoFactorProvider.remember,
twoFactorRememberedToken, false, deviceRequest);
}
else {
request = new TokenRequest(email, hashedPassword, null, null, false, deviceRequest);
}
apiService.postIdentityToken(request, function (response) { apiService.postIdentityToken(request, function (response) {
// success // success
@ -20,10 +33,15 @@
return; return;
} }
if (response.twoFactorToken) {
tokenService.setTwoFactorToken(response.twoFactorToken, email, function () { });
}
tokenService.setTokens(response.accessToken, response.refreshToken, function () { tokenService.setTokens(response.accessToken, response.refreshToken, function () {
cryptoService.setKey(key, function () { cryptoService.setKey(key, function () {
cryptoService.setKeyHash(hashedPassword, function () { cryptoService.setKeyHash(hashedPassword, function () {
userService.setUserIdAndEmail(tokenService.getUserId(), tokenService.getEmail(), function () { userService.setUserIdAndEmail(tokenService.getUserId(), tokenService.getEmail(),
function () {
cryptoService.setEncKey(response.key).then(function () { cryptoService.setEncKey(response.key).then(function () {
return cryptoService.setEncPrivateKey(response.privateKey); return cryptoService.setEncPrivateKey(response.privateKey);
}).then(function () { }).then(function () {
@ -49,6 +67,7 @@
}); });
}); });
}); });
});
return deferred.promise; return deferred.promise;
}; };

View File

@ -65,3 +65,13 @@
margin: 0 auto; margin: 0 auto;
} }
} }
.two-factor-key-page {
padding: 20px 20px 0 20px;
text-align: center;
.img-responsive {
margin-left: auto;
margin-right: auto;
}
}

View File

@ -45,7 +45,9 @@ function initApiService() {
error: function (jqXHR, textStatus, errorThrown) { error: function (jqXHR, textStatus, errorThrown) {
if (jqXHR.responseJSON && jqXHR.responseJSON.TwoFactorProviders2 && if (jqXHR.responseJSON && jqXHR.responseJSON.TwoFactorProviders2 &&
Object.keys(jqXHR.responseJSON.TwoFactorProviders2).length) { Object.keys(jqXHR.responseJSON.TwoFactorProviders2).length) {
self.tokenService.clearTwoFactorToken(tokenRequest.email, function () {
successWithTwoFactor(jqXHR.responseJSON.TwoFactorProviders2); successWithTwoFactor(jqXHR.responseJSON.TwoFactorProviders2);
});
} }
else { else {
error(new ErrorResponse(jqXHR, true)); error(new ErrorResponse(jqXHR, true));
@ -56,12 +58,14 @@ function initApiService() {
// Two Factor APIs // Two Factor APIs
ApiService.prototype.postTwoFactorEmail = function (success, error) { ApiService.prototype.postTwoFactorEmail = function (request, success, error) {
var self = this; var self = this;
$.ajax({ $.ajax({
type: 'POST', type: 'POST',
url: self.baseUrl + '/two-factor/send-email?' + token, url: self.baseUrl + '/two-factor/send-email-login',
dataType: 'json', dataType: 'text',
contentType: 'application/json; charset=utf-8',
data: JSON.stringify(request),
success: function (response) { success: function (response) {
success(response); success(response);
}, },

View File

@ -102,6 +102,46 @@ function initTokenService() {
}); });
}; };
TokenService.prototype.setTwoFactorToken = function (token, email, callback) {
if (!callback || typeof callback !== 'function') {
throw 'callback function required';
}
var obj = {};
obj['twoFactorToken_' + email] = token;
chrome.storage.local.set(obj, function () {
callback();
});
};
TokenService.prototype.getTwoFactorToken = function (email, callback) {
if (!callback || typeof callback !== 'function') {
throw 'callback function required';
}
var prop = 'twoFactorToken_' + email;
chrome.storage.local.get(prop, function (obj) {
if (obj && obj[prop]) {
callback(obj[prop]);
return;
}
return callback(null);
});
};
TokenService.prototype.clearTwoFactorToken = function (email, callback) {
if (!callback || typeof callback !== 'function') {
throw 'callback function required';
}
chrome.storage.local.remove('twoFactorToken_' + email, function () {
callback();
});
};
TokenService.prototype.clearAuthBearer = function (callback) { TokenService.prototype.clearAuthBearer = function (callback) {
if (!callback || typeof callback !== 'function') { if (!callback || typeof callback !== 'function') {
throw 'callback function required'; throw 'callback function required';