1
0
mirror of https://github.com/bitwarden/browser.git synced 2024-11-24 12:06:15 +01:00

Added two factor flow into identity login

This commit is contained in:
Kyle Spearrin 2017-01-18 22:14:51 -05:00
parent 6bb6c7074b
commit 0b63eb58ba
8 changed files with 88 additions and 154 deletions

View File

@ -12,9 +12,11 @@ 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, device) { var TokenRequest = function (email, masterPasswordHash, code, device) {
this.email = email; this.email = email;
this.masterPasswordHash = masterPasswordHash; this.masterPasswordHash = masterPasswordHash;
this.code = code;
this.provider = 'Authenticator';
this.device = null; this.device = null;
if (device) { if (device) {
this.device = new DeviceRequest(device); this.device = new DeviceRequest(device);
@ -36,6 +38,11 @@ var TokenRequest = function (email, masterPasswordHash, device) {
obj.devicePushToken = this.device.pushToken; obj.devicePushToken = this.device.pushToken;
} }
if (this.code && this.provider) {
obj.twoFactorCode = this.code;
obj.twoFactorProvider = this.provider;
}
return obj; return obj;
}; };
}; };
@ -51,12 +58,6 @@ var PasswordHintRequest = function (email) {
this.email = email; this.email = email;
}; };
var TokenTwoFactorRequest = function (code) {
this.code = code;
this.provider = "Authenticator";
this.device = null;
};
var DeviceTokenRequest = function () { var DeviceTokenRequest = function () {
this.pushToken = null; this.pushToken = null;
}; };

View File

@ -38,14 +38,6 @@ var ProfileResponse = function (response) {
this.twoFactorEnabled = response.TwoFactorEnabled; this.twoFactorEnabled = response.TwoFactorEnabled;
}; };
var TokenResponse = function (response) {
this.token = response.Token;
if (response.Profile) {
this.profile = new ProfileResponse(response.Profile);
}
};
var IdentityTokenResponse = function (response) { var IdentityTokenResponse = function (response) {
this.accessToken = response.access_token; this.accessToken = response.access_token;
this.expiresIn = response.expires_in; this.expiresIn = response.expires_in;
@ -59,10 +51,18 @@ var ListResponse = function (data) {
this.data = data; this.data = data;
}; };
var ErrorResponse = function (response) { var ErrorResponse = function (response, identityResponse) {
if (response.responseJSON) { var errorModel = null;
this.message = response.responseJSON.Message; if (identityResponse && identityResponse === true && response.responseJSON && response.responseJSON.ErrorModel) {
this.validationErrors = response.responseJSON.ValidationErrors; errorModel = response.responseJSON.ErrorModel;
}
else if (response.responseJSON) {
errorModel = response.responseJSON;
}
if (errorModel) {
this.message = errorModel.Message;
this.validationErrors = errorModel.ValidationErrors;
} }
this.statusCode = response.status; this.statusCode = response.status;
}; };

View File

@ -32,19 +32,24 @@
return; return;
} }
$scope.loginPromise = authService.logIn(model.email, model.masterPassword); $scope.loginPromise = authService.logIn(model.email, model.masterPassword, null);
$scope.loginPromise.then(function () { $scope.loginPromise.then(function (twoFactor) {
userService.isTwoFactorAuthenticated(function (isTwoFactorAuthenticated) { if (twoFactor) {
if (isTwoFactorAuthenticated) {
$analytics.eventTrack('Logged In To Two-step'); $analytics.eventTrack('Logged In To Two-step');
$state.go('twoFactor', { animation: 'in-slide-left' }); $state.go('twoFactor', {
animation: 'in-slide-left',
email: model.email,
masterPassword: model.masterPassword
});
} }
else { else {
$analytics.eventTrack('Logged In'); $analytics.eventTrack('Logged In');
$state.go('tabs.vault', { animation: 'in-slide-left', syncOnLoad: true }); $state.go('tabs.vault', {
} animation: 'in-slide-left',
syncOnLoad: true
}); });
}
}); });
}; };
}); });

View File

@ -2,12 +2,15 @@
.module('bit.accounts') .module('bit.accounts')
.controller('accountsLoginTwoFactorController', function ($scope, $state, authService, toastr, utilsService, .controller('accountsLoginTwoFactorController', function ($scope, $state, authService, toastr, utilsService,
$analytics, i18nService) { $analytics, i18nService, $stateParams) {
$scope.i18n = i18nService; $scope.i18n = i18nService;
$scope.model = {}; $scope.model = {};
utilsService.initListSectionItemListeners($(document), angular); utilsService.initListSectionItemListeners($(document), angular);
$('#code').focus(); $('#code').focus();
var email = $stateParams.email;
var masterPassword = $stateParams.masterPassword;
$scope.loginPromise = null; $scope.loginPromise = null;
$scope.login = function (model) { $scope.login = function (model) {
if (!model.code) { if (!model.code) {
@ -15,7 +18,7 @@
return; return;
} }
$scope.loginPromise = authService.logInTwoFactor(model.code); $scope.loginPromise = authService.logIn(email, masterPassword, model.code);
$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 });

View File

@ -49,7 +49,7 @@
controller: 'accountsLoginTwoFactorController', controller: 'accountsLoginTwoFactorController',
templateUrl: 'app/accounts/views/accountsLoginTwoFactor.html', templateUrl: 'app/accounts/views/accountsLoginTwoFactor.html',
data: { authorize: false }, data: { authorize: false },
params: { animation: null } params: { animation: null, email: null, masterPassword: null }
}) })
.state('register', { .state('register', {
url: '/register', url: '/register',

View File

@ -5,14 +5,15 @@
folderService, settingsService, syncService) { folderService, settingsService, syncService) {
var _service = {}; var _service = {};
_service.logIn = function (email, masterPassword) { _service.logIn = function (email, masterPassword, twoFactorCode) {
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) {
var request = new TokenRequest(email, hashedPassword); var request = new TokenRequest(email, hashedPassword, twoFactorCode);
apiService.postIdentityToken(request, function (response) { apiService.postIdentityToken(request, function (response) {
// success
if (!response || !response.accessToken) { if (!response || !response.accessToken) {
return; return;
} }
@ -20,52 +21,24 @@
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 () {
if (tokenService.isTwoFactorScheme()) { userService.setUserIdAndEmail(tokenService.getUserId(), tokenService.getEmail(), function () {
deferred.resolve(response);
}
else {
userService.setUserId(tokenService.getUserId(), function () {
userService.setEmail(tokenService.getEmail(), function () {
chrome.runtime.sendMessage({ command: 'loggedIn' }); chrome.runtime.sendMessage({ command: 'loggedIn' });
deferred.resolve(response); deferred.resolve(false);
});
});
}
}); });
}); });
}); });
});
}, function () {
// two factor required
deferred.resolve(true);
}, function (error) { }, function (error) {
// error
deferred.reject(error); deferred.reject(error);
}); });
}); });
return deferred.promise; return deferred.promise;
}; };
_service.logInTwoFactor = function (code) {
var request = new TokenTwoFactorRequest(code.replace(' ', ''));
var deferred = $q.defer();
apiService.postTokenTwoFactor(request, function (response) {
if (!response || !response.token) {
deferred.reject();
return;
}
tokenService.setToken(response.token, function () {
userService.setUserId(response.profile.id, function () {
userService.setEmail(response.profile.email, function () {
chrome.runtime.sendMessage({ command: 'loggedIn' });
deferred.resolve(response);
});
});
});
}, function (error) {
deferred.reject(error);
});
return deferred.promise;
};
// TODO: Fix callback hell by moving to promises // TODO: Fix callback hell by moving to promises
_service.logOut = function (callback) { _service.logOut = function (callback) {
userService.getUserId(function (userId) { userService.getUserId(function (userId) {
@ -74,8 +47,7 @@
tokenService.clearToken(function () { tokenService.clearToken(function () {
cryptoService.clearKey(function () { cryptoService.clearKey(function () {
cryptoService.clearKeyHash(function () { cryptoService.clearKeyHash(function () {
userService.clearUserId(function () { userService.clearUserIdAndEmail(function () {
userService.clearEmail(function () {
loginService.clear(userId, function () { loginService.clear(userId, function () {
folderService.clear(userId, function () { folderService.clear(userId, function () {
$rootScope.vaultLogins = null; $rootScope.vaultLogins = null;
@ -91,7 +63,6 @@
}); });
}); });
}); });
});
}; };
return _service; return _service;

View File

@ -1,5 +1,5 @@
function ApiService(tokenService) { function ApiService(tokenService) {
this.baseUrl = 'https://api.bitwarden.com'; this.baseUrl = 'http://localhost:4000';
this.tokenService = tokenService; this.tokenService = tokenService;
initApiService(); initApiService();
@ -8,26 +8,7 @@ function ApiService(tokenService) {
function initApiService() { function initApiService() {
// Auth APIs // Auth APIs
ApiService.prototype.postTokenTwoFactor = function (twoFactorTokenRequest, success, error) { ApiService.prototype.postIdentityToken = function (tokenRequest, success, successWithTwoFactor, error) {
var self = this;
this.tokenService.getToken(function (token) {
$.ajax({
type: 'POST',
url: self.baseUrl + '/auth/token/two-factor?access_token2=' + token,
data: JSON.stringify(twoFactorTokenRequest),
contentType: 'application/json; charset=utf-8',
dataType: 'json',
success: function (response) {
success(new TokenResponse(response));
},
error: function (jqXHR, textStatus, errorThrown) {
handleError(error, jqXHR, textStatus, errorThrown);
}
});
});
};
ApiService.prototype.postIdentityToken = function (tokenRequest, success, error) {
var self = this; var self = this;
$.ajax({ $.ajax({
@ -40,7 +21,13 @@ function initApiService() {
success(new IdentityTokenResponse(response)); success(new IdentityTokenResponse(response));
}, },
error: function (jqXHR, textStatus, errorThrown) { error: function (jqXHR, textStatus, errorThrown) {
handleError(error, jqXHR, textStatus, errorThrown); if (jqXHR.responseJSON && jqXHR.responseJSON.TwoFactorRequired &&
jqXHR.responseJSON.TwoFactorRequired === true) {
successWithTwoFactor();
}
else {
error(new ErrorResponse(jqXHR, true));
}
} }
}); });
}; };

View File

@ -13,17 +13,24 @@ function initUserService() {
var _userId = null, var _userId = null,
_email = null; _email = null;
UserService.prototype.setUserId = function (userId, callback) { UserService.prototype.setUserIdAndEmail = function (userId, email, callback) {
if (!callback || typeof callback !== 'function') { if (!callback || typeof callback !== 'function') {
throw 'callback function required'; throw 'callback function required';
} }
_email = email;
var emailObj = {};
emailObj[userEmailKey] = email;
_userId = userId; _userId = userId;
var obj = {}; var userIdObj = {};
obj[userIdKey] = userId; userIdObj[userIdKey] = userId;
chrome.storage.local.set(obj, function () {
chrome.storage.local.set(userIdObj, function () {
chrome.storage.local.set(emailObj, function () {
callback(); callback();
}); });
});
}; };
UserService.prototype.getUserId = function (callback) { UserService.prototype.getUserId = function (callback) {
@ -44,30 +51,6 @@ function initUserService() {
}); });
}; };
UserService.prototype.clearUserId = function (callback) {
if (!callback || typeof callback !== 'function') {
throw 'callback function required';
}
_userId = null;
chrome.storage.local.remove(userIdKey, function () {
callback();
});
};
UserService.prototype.setEmail = function (email, callback) {
if (!callback || typeof callback !== 'function') {
throw 'callback function required';
}
_email = email;
var obj = {};
obj[userEmailKey] = email;
chrome.storage.local.set(obj, function () {
callback();
});
};
UserService.prototype.getEmail = function (callback) { UserService.prototype.getEmail = function (callback) {
if (!callback || typeof callback !== 'function') { if (!callback || typeof callback !== 'function') {
throw 'callback function required'; throw 'callback function required';
@ -86,15 +69,17 @@ function initUserService() {
}); });
}; };
UserService.prototype.clearEmail = function (callback) { UserService.prototype.clearUserIdAndEmail = function (callback) {
if (!callback || typeof callback !== 'function') { if (!callback || typeof callback !== 'function') {
throw 'callback function required'; throw 'callback function required';
} }
_email = null; _userId = _email = null;
chrome.storage.local.remove(userIdKey, function () {
chrome.storage.local.remove(userEmailKey, function () { chrome.storage.local.remove(userEmailKey, function () {
callback(); callback();
}); });
});
}; };
UserService.prototype.isAuthenticated = function (callback) { UserService.prototype.isAuthenticated = function (callback) {
@ -114,22 +99,4 @@ function initUserService() {
} }
}); });
}; };
UserService.prototype.isTwoFactorAuthenticated = function (callback) {
if (!callback || typeof callback !== 'function') {
throw 'callback function required';
}
var self = this;
self.tokenService.getToken(function (token) {
if (!token) {
callback(false);
}
else {
self.getUserId(function (userId) {
callback(userId === null);
});
}
});
};
}; };