mirror of
https://github.com/bitwarden/browser
synced 2025-12-15 15:53:27 +00:00
two-factor support in browser extensions
This commit is contained in:
@@ -40,7 +40,8 @@
|
||||
$state.go('twoFactor', {
|
||||
animation: 'in-slide-left',
|
||||
email: model.email,
|
||||
masterPassword: model.masterPassword
|
||||
masterPassword: model.masterPassword,
|
||||
providers: response.twoFactorProviders
|
||||
});
|
||||
}
|
||||
else {
|
||||
|
||||
@@ -2,23 +2,30 @@
|
||||
.module('bit.accounts')
|
||||
|
||||
.controller('accountsLoginTwoFactorController', function ($scope, $state, authService, toastr, utilsService,
|
||||
$analytics, i18nService, $stateParams) {
|
||||
$analytics, i18nService, $stateParams, $filter, constantsService, $timeout, $window, cryptoService) {
|
||||
$scope.i18n = i18nService;
|
||||
$scope.model = {};
|
||||
utilsService.initListSectionItemListeners($(document), angular);
|
||||
$('#code').focus();
|
||||
|
||||
var constants = constantsService;
|
||||
var email = $stateParams.email;
|
||||
var masterPassword = $stateParams.masterPassword;
|
||||
var providers = $stateParams.providers;
|
||||
|
||||
$scope.twoFactorEmail = null;
|
||||
$scope.token = null;
|
||||
$scope.constantsProvider = constants.twoFactorProvider;
|
||||
$scope.providerType = $stateParams.provider ? $stateParams.provider : getDefaultProvider(providers);
|
||||
$scope.u2fReady = false;
|
||||
init();
|
||||
|
||||
$scope.loginPromise = null;
|
||||
$scope.login = function (model) {
|
||||
if (!model.code) {
|
||||
$scope.login = function (token) {
|
||||
if (!token) {
|
||||
toastr.error(i18nService.verificationCodeRequired, i18nService.errorsOccurred);
|
||||
return;
|
||||
}
|
||||
|
||||
$scope.loginPromise = authService.logIn(email, masterPassword, 0, model.code);
|
||||
$scope.loginPromise = authService.logIn(email, masterPassword, $scope.providerType, token);
|
||||
$scope.loginPromise.then(function () {
|
||||
$analytics.eventTrack('Logged In From Two-step');
|
||||
$state.go('tabs.vault', { animation: 'in-slide-left', syncOnLoad: true });
|
||||
@@ -29,4 +36,94 @@
|
||||
$analytics.eventTrack('Selected Lost 2FA App');
|
||||
chrome.tabs.create({ url: 'https://help.bitwarden.com/article/lost-two-step-device/' });
|
||||
};
|
||||
|
||||
$scope.sendEmail = function (doToast) {
|
||||
if ($scope.providerType !== constants.twoFactorProvider.email) {
|
||||
return;
|
||||
}
|
||||
|
||||
var key = cryptoService.makeKey(masterPassword, email);
|
||||
var hash = cryptoService.hashPassword(masterPassword, key);
|
||||
apiService.postTwoFactorEmail({
|
||||
email: email,
|
||||
masterPasswordHash: hash
|
||||
}, function () {
|
||||
if (doToast) {
|
||||
toastr.success('Verification email sent to ' + $scope.twoFactorEmail + '.');
|
||||
}
|
||||
}, function () {
|
||||
toastr.error('Could not send verification email.');
|
||||
});
|
||||
};
|
||||
|
||||
function getDefaultProvider(twoFactorProviders) {
|
||||
var keys = Object.keys(twoFactorProviders);
|
||||
var providerType = null;
|
||||
var providerPriority = -1;
|
||||
for (var i = 0; i < keys.length; i++) {
|
||||
var provider = $filter('filter')(constants.twoFactorProviderInfo, { type: keys[i], active: true });
|
||||
if (provider.length && provider[0].priority > providerPriority) {
|
||||
if (provider[0].type == constants.twoFactorProvider.u2f &&
|
||||
!utilsService.isChrome() && !utilsService.isOpera()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
providerType = provider[0].type;
|
||||
providerPriority = provider[0].priority;
|
||||
}
|
||||
}
|
||||
return parseInt(providerType);
|
||||
}
|
||||
|
||||
function init() {
|
||||
$timeout(function () {
|
||||
$('#code').focus();
|
||||
|
||||
if ($scope.providerType === constants.twoFactorProvider.duo) {
|
||||
var params = providers[constants.twoFactorProvider.duo];
|
||||
|
||||
$window.Duo.init({
|
||||
host: params.Host,
|
||||
sig_request: params.Signature,
|
||||
submit_callback: function (theForm) {
|
||||
var response = $(theForm).find('input[name="sig_response"]').val();
|
||||
$scope.login(response);
|
||||
}
|
||||
});
|
||||
}
|
||||
else if ($scope.providerType === constants.twoFactorProvider.u2f) {
|
||||
var params = providers[constants.twoFactorProvider.u2f];
|
||||
var challenges = JSON.parse(params.Challenges);
|
||||
|
||||
var u2f = new U2f(function (data) {
|
||||
$scope.login(data);
|
||||
$scope.$apply();
|
||||
}, function (error) {
|
||||
toastr.error(error, i18nService.errorsOccurred);
|
||||
$scope.$apply();
|
||||
}, function (info) {
|
||||
if (info === 'ready') {
|
||||
$scope.u2fReady = true;
|
||||
}
|
||||
$scope.$apply();
|
||||
});
|
||||
|
||||
u2f.init({
|
||||
appId: challenges[0].appId,
|
||||
challenge: challenges[0].challenge,
|
||||
keys: [{
|
||||
version: challenges[0].version,
|
||||
keyHandle: challenges[0].keyHandle
|
||||
}]
|
||||
});
|
||||
}
|
||||
else if ($scope.providerType === constants.twoFactorProvider.email) {
|
||||
var params = providers[constants.twoFactorProvider.email];
|
||||
$scope.twoFactorEmail = params.Email;
|
||||
if (Object.keys(providers).length > 1) {
|
||||
$scope.sendEmail(false);
|
||||
}
|
||||
}
|
||||
}, 500);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
<form name="theForm" ng-submit="login(model)" bit-form="loginPromise">
|
||||
<form name="theForm" ng-submit="login(token)" bit-form="loginPromise"
|
||||
ng-if="providerType === constantsProvider.authenticator || providerType === constantsProvider.email">
|
||||
<div class="header">
|
||||
<div class="left">
|
||||
<a ui-sref="login({animation: 'out-slide-right'})"><i class="fa fa-chevron-left"></i> {{i18n.login}}</a>
|
||||
@@ -16,7 +17,7 @@
|
||||
<div class="list-section-item list-section-item-icon-input">
|
||||
<i class="fa fa-lock fa-lg fa-fw"></i>
|
||||
<label for="code" class="sr-only">{{i18n.verificationCode}}</label>
|
||||
<input id="code" type="text" name="Code" placeholder="{{i18n.verificationCode}}" ng-model="model.code">
|
||||
<input id="code" type="text" name="Code" placeholder="{{i18n.verificationCode}}" ng-model="token">
|
||||
</div>
|
||||
</div>
|
||||
<div class="list-section-footer">
|
||||
@@ -29,3 +30,63 @@
|
||||
</p>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<form name="theForm" bit-form="loginPromise" ng-if="providerType === constantsProvider.duo">
|
||||
<div class="header">
|
||||
<div class="left">
|
||||
<a ui-sref="login({animation: 'out-slide-right'})"><i class="fa fa-chevron-left"></i> {{i18n.login}}</a>
|
||||
</div>
|
||||
<div class="title">Duo</div>
|
||||
</div>
|
||||
<div class="content">
|
||||
<div id="duoFrameWrapper">
|
||||
<iframe id="duo_iframe"></iframe>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<form name="theForm" ng-submit="login(token)" bit-form="loginPromise" ng-if="providerType === constantsProvider.yubikey">
|
||||
<div class="header">
|
||||
<div class="left">
|
||||
<a ui-sref="login({animation: 'out-slide-right'})"><i class="fa fa-chevron-left"></i> {{i18n.login}}</a>
|
||||
</div>
|
||||
<div class="right">
|
||||
<button type="submit" class="btn btn-link" ng-show="!theForm.$loading">{{i18n.continue}}</button>
|
||||
<i class="fa fa-spinner fa-lg fa-spin" ng-show="theForm.$loading"></i>
|
||||
</div>
|
||||
<div class="title">YubiKey</div>
|
||||
</div>
|
||||
<div class="content">
|
||||
<div class="list">
|
||||
<div class="list-section">
|
||||
<div class="list-section-items">
|
||||
<div class="list-section-item list-section-item-icon-input">
|
||||
<i class="fa fa-lock fa-lg fa-fw"></i>
|
||||
<label for="code" class="sr-only">{{i18n.verificationCode}}</label>
|
||||
<input id="code" type="password" name="Code" ng-model="token">
|
||||
</div>
|
||||
</div>
|
||||
<div class="list-section-footer">
|
||||
Touch the YubiKey button.
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<form name="theForm" bit-form="loginPromise" ng-if="providerType === constantsProvider.u2f">
|
||||
<div class="header">
|
||||
<div class="left">
|
||||
<a ui-sref="login({animation: 'out-slide-right'})"><i class="fa fa-chevron-left"></i> {{i18n.login}}</a>
|
||||
</div>
|
||||
<div class="right">
|
||||
<i class="fa fa-spinner fa-lg fa-spin" ng-show="theForm.$loading"></i>
|
||||
</div>
|
||||
<div class="title">FIDO U2F</div>
|
||||
</div>
|
||||
<div class="content">
|
||||
<div ng-if="!u2fReady">Loading...</div>
|
||||
<div ng-if="u2fReady">Touch button</div>
|
||||
<iframe id="u2f_iframe"></iframe>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
@@ -66,7 +66,7 @@
|
||||
controller: 'accountsLoginTwoFactorController',
|
||||
templateUrl: 'app/accounts/views/accountsLoginTwoFactor.html',
|
||||
data: { authorize: false },
|
||||
params: { animation: null, email: null, masterPassword: null }
|
||||
params: { animation: null, email: null, masterPassword: null, providers: null, provider: null }
|
||||
})
|
||||
.state('register', {
|
||||
url: '/register',
|
||||
|
||||
@@ -17,6 +17,8 @@
|
||||
<script src="../lib/papaparse/papaparse.js"></script>
|
||||
<script src="../lib/clipboard/clipboard.js"></script>
|
||||
<script src="../scripts/analytics.js"></script>
|
||||
<script src="../scripts/duo.js"></script>
|
||||
<script src="../scripts/u2f.js"></script>
|
||||
|
||||
<script src="../lib/angular/angular.js"></script>
|
||||
<script src="../lib/angular-animate/angular-animate.js"></script>
|
||||
|
||||
@@ -493,3 +493,16 @@
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
#duoFrameWrapper {
|
||||
background: ~"url('../../images/loading.svg') 0 0 no-repeat";
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
|
||||
iframe {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border: none;
|
||||
margin-bottom: -5px;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user