diff --git a/src/Api/Controllers/TwoFactorController.cs b/src/Api/Controllers/TwoFactorController.cs index ba3fd3ade6..d252b949cc 100644 --- a/src/Api/Controllers/TwoFactorController.cs +++ b/src/Api/Controllers/TwoFactorController.cs @@ -43,7 +43,7 @@ namespace Bit.Api.Controllers [HttpPost("get-authenticator")] public async Task GetAuthenticator([FromBody]TwoFactorRequestModel model) { - var user = await GetProviderAsync(model, TwoFactorProviderType.Authenticator); + var user = await CheckPasswordAsync(model.MasterPasswordHash); var response = new TwoFactorAuthenticatorResponseModel(user); return response; } @@ -53,17 +53,8 @@ namespace Bit.Api.Controllers public async Task PutAuthenticator( [FromBody]UpdateTwoFactorAuthenticatorRequestModel model) { - var user = await _userService.GetUserByPrincipalAsync(User); - if(user == null) - { - throw new UnauthorizedAccessException(); - } - - if(!await _userManager.CheckPasswordAsync(user, model.MasterPasswordHash)) - { - await Task.Delay(2000); - throw new BadRequestException("MasterPasswordHash", "Invalid password."); - } + var user = await CheckPasswordAsync(model.MasterPasswordHash); + model.ToUser(user); if(!await _userManager.VerifyTwoFactorTokenAsync(user, TwoFactorProviderType.Authenticator.ToString(), model.Token)) { @@ -79,7 +70,7 @@ namespace Bit.Api.Controllers [HttpPost("get-email")] public async Task GetEmail([FromBody]TwoFactorRequestModel model) { - var user = await GetProviderAsync(model, TwoFactorProviderType.Email); + var user = await CheckPasswordAsync(model.MasterPasswordHash); var response = new TwoFactorEmailResponseModel(user); return response; } @@ -87,51 +78,25 @@ namespace Bit.Api.Controllers [HttpPost("send-email")] public async Task SendEmail([FromBody]TwoFactorEmailRequestModel model) { - var user = await _userService.GetUserByPrincipalAsync(User); - if(user == null) - { - throw new UnauthorizedAccessException(); - } - - if(!await _userManager.CheckPasswordAsync(user, model.MasterPasswordHash)) - { - await Task.Delay(2000); - throw new BadRequestException("MasterPasswordHash", "Invalid password."); - } - - await _userService.SendTwoFactorEmailAsync(user, model.Email); + var user = await CheckPasswordAsync(model.MasterPasswordHash); + model.ToUser(user); + await _userService.SendTwoFactorEmailAsync(user); } [HttpPut("email")] [HttpPost("email")] public async Task PutEmail([FromBody]UpdateTwoFactorEmailRequestModel model) { - var user = await _userService.GetUserByPrincipalAsync(User); - if(user == null) - { - throw new UnauthorizedAccessException(); - } + var user = await CheckPasswordAsync(model.MasterPasswordHash); + model.ToUser(user); - if(!await _userManager.CheckPasswordAsync(user, model.MasterPasswordHash)) - { - await Task.Delay(2000); - throw new BadRequestException("MasterPasswordHash", "Invalid password."); - } - - if(!await _userService.VerifyTwoFactorEmailAsync(user, model.Token, model.Email)) + if(!await _userService.VerifyTwoFactorEmailAsync(user, model.Token)) { await Task.Delay(2000); throw new BadRequestException("Token", "Invalid token."); } - var providers = user.GetTwoFactorProviders(); - providers[TwoFactorProviderType.Email] = new Core.Models.TwoFactorProvider - { - MetaData = new System.Collections.Generic.Dictionary { ["Email"] = model.Email } - }; - user.SetTwoFactorProviders(providers); await _userService.UpdateTwoFactorProviderAsync(user, TwoFactorProviderType.Email); - var response = new TwoFactorEmailResponseModel(user); return response; } @@ -140,18 +105,7 @@ namespace Bit.Api.Controllers [HttpPost("disable")] public async Task PutDisable([FromBody]TwoFactorProviderRequestModel model) { - var user = await _userService.GetUserByPrincipalAsync(User); - if(user == null) - { - throw new UnauthorizedAccessException(); - } - - if(!await _userManager.CheckPasswordAsync(user, model.MasterPasswordHash)) - { - await Task.Delay(2000); - throw new BadRequestException("MasterPasswordHash", "Invalid password."); - } - + var user = await CheckPasswordAsync(model.MasterPasswordHash); await _userService.DisableTwoFactorProviderAsync(user, model.Type.Value); var response = new TwoFactorEmailResponseModel(user); @@ -169,7 +123,7 @@ namespace Bit.Api.Controllers } } - private async Task GetProviderAsync(TwoFactorRequestModel model, TwoFactorProviderType type) + private async Task CheckPasswordAsync(string masterPasswordHash) { var user = await _userService.GetUserByPrincipalAsync(User); if(user == null) @@ -177,13 +131,12 @@ namespace Bit.Api.Controllers throw new UnauthorizedAccessException(); } - if(!await _userManager.CheckPasswordAsync(user, model.MasterPasswordHash)) + if(!await _userManager.CheckPasswordAsync(user, masterPasswordHash)) { await Task.Delay(2000); throw new BadRequestException("MasterPasswordHash", "Invalid password."); } - - await _userService.SetupTwoFactorAsync(user, type); + return user; } } diff --git a/src/Core/Identity/AuthenticatorTokenProvider.cs b/src/Core/Identity/AuthenticatorTokenProvider.cs index 4919079f20..0c9241ac3f 100644 --- a/src/Core/Identity/AuthenticatorTokenProvider.cs +++ b/src/Core/Identity/AuthenticatorTokenProvider.cs @@ -14,8 +14,6 @@ namespace Bit.Core.Identity var provider = user.GetTwoFactorProvider(TwoFactorProviderType.Authenticator); var canGenerate = user.TwoFactorProviderIsEnabled(TwoFactorProviderType.Authenticator) - && user.TwoFactorProvider.HasValue - && user.TwoFactorProvider.Value == TwoFactorProviderType.Authenticator && !string.IsNullOrWhiteSpace(provider.MetaData["Key"]); return Task.FromResult(canGenerate); diff --git a/src/Core/Identity/DuoTokenProvider.cs b/src/Core/Identity/DuoTokenProvider.cs index 9847bc54d5..a34259af8c 100644 --- a/src/Core/Identity/DuoTokenProvider.cs +++ b/src/Core/Identity/DuoTokenProvider.cs @@ -13,10 +13,7 @@ namespace Bit.Core.Identity public Task CanGenerateTwoFactorTokenAsync(UserManager manager, User user) { var provider = user.GetTwoFactorProvider(TwoFactorProviderType.Duo); - var canGenerate = user.TwoFactorProviderIsEnabled(TwoFactorProviderType.Duo) - && user.TwoFactorProvider.HasValue - && user.TwoFactorProvider.Value == TwoFactorProviderType.Duo && !string.IsNullOrWhiteSpace(provider?.MetaData["UserId"]); return Task.FromResult(canGenerate); diff --git a/src/Core/Identity/YubicoOtpTokenProvider.cs b/src/Core/Identity/YubicoOtpTokenProvider.cs index f7fb1601fe..4ff9d60858 100644 --- a/src/Core/Identity/YubicoOtpTokenProvider.cs +++ b/src/Core/Identity/YubicoOtpTokenProvider.cs @@ -19,10 +19,7 @@ namespace Bit.Core.Identity public Task CanGenerateTwoFactorTokenAsync(UserManager manager, User user) { var provider = user.GetTwoFactorProvider(TwoFactorProviderType.YubiKey); - var canGenerate = user.TwoFactorProviderIsEnabled(TwoFactorProviderType.YubiKey) - && user.TwoFactorProvider.HasValue - && user.TwoFactorProvider.Value == TwoFactorProviderType.YubiKey && (provider?.MetaData.Values.Any(v => !string.IsNullOrWhiteSpace(v)) ?? false); return Task.FromResult(canGenerate); diff --git a/src/Core/Models/Api/Request/TwoFactorRequestModels.cs b/src/Core/Models/Api/Request/TwoFactorRequestModels.cs index a4a39b4e4f..a26b251d12 100644 --- a/src/Core/Models/Api/Request/TwoFactorRequestModels.cs +++ b/src/Core/Models/Api/Request/TwoFactorRequestModels.cs @@ -1,4 +1,5 @@ -using System; +using Bit.Core.Enums; +using Bit.Core.Models.Table; using System.Collections.Generic; using System.ComponentModel.DataAnnotations; @@ -9,6 +10,30 @@ namespace Bit.Core.Models.Api [Required] [StringLength(50)] public string Token { get; set; } + [Required] + [StringLength(50)] + public string Key { get; set; } + + public User ToUser(User extistingUser) + { + var providers = extistingUser.GetTwoFactorProviders(); + if(providers == null) + { + providers = new Dictionary(); + } + else if(providers.ContainsKey(TwoFactorProviderType.Authenticator)) + { + providers.Remove(TwoFactorProviderType.Authenticator); + } + + providers.Add(TwoFactorProviderType.Authenticator, new TwoFactorProvider + { + MetaData = new Dictionary { ["Key"] = Key }, + Enabled = true + }); + extistingUser.SetTwoFactorProviders(providers); + return extistingUser; + } } public class UpdateTwoFactorDuoRequestModel : TwoFactorRequestModel @@ -48,6 +73,27 @@ namespace Bit.Core.Models.Api [EmailAddress] [StringLength(50)] public string Email { get; set; } + + public User ToUser(User extistingUser) + { + var providers = extistingUser.GetTwoFactorProviders(); + if(providers == null) + { + providers = new Dictionary(); + } + else if(providers.ContainsKey(TwoFactorProviderType.Email)) + { + providers.Remove(TwoFactorProviderType.Email); + } + + providers.Add(TwoFactorProviderType.Email, new TwoFactorProvider + { + MetaData = new Dictionary { ["Email"] = Email }, + Enabled = true + }); + extistingUser.SetTwoFactorProviders(providers); + return extistingUser; + } } public class UpdateTwoFactorEmailRequestModel : TwoFactorEmailRequestModel diff --git a/src/Core/Models/Api/Response/TwoFactor/TwoFactorAuthenticatorResponseModel.cs b/src/Core/Models/Api/Response/TwoFactor/TwoFactorAuthenticatorResponseModel.cs index 2b4f03ea7d..9401d38995 100644 --- a/src/Core/Models/Api/Response/TwoFactor/TwoFactorAuthenticatorResponseModel.cs +++ b/src/Core/Models/Api/Response/TwoFactor/TwoFactorAuthenticatorResponseModel.cs @@ -1,6 +1,7 @@ using System; using Bit.Core.Enums; using Bit.Core.Models.Table; +using OtpNet; namespace Bit.Core.Models.Api { @@ -22,6 +23,8 @@ namespace Bit.Core.Models.Api } else { + var key = KeyGeneration.GenerateRandomKey(20); + Key = Base32Encoding.ToString(key); Enabled = false; } } diff --git a/src/Core/Services/IUserService.cs b/src/Core/Services/IUserService.cs index baf8e21fc2..302837ab37 100644 --- a/src/Core/Services/IUserService.cs +++ b/src/Core/Services/IUserService.cs @@ -19,8 +19,8 @@ namespace Bit.Core.Services Task SaveUserAsync(User user); Task RegisterUserAsync(User user, string masterPassword); Task SendMasterPasswordHintAsync(string email); - Task SendTwoFactorEmailAsync(User user, string email = null); - Task VerifyTwoFactorEmailAsync(User user, string token, string email = null); + Task SendTwoFactorEmailAsync(User user); + Task VerifyTwoFactorEmailAsync(User user, string token); Task InitiateEmailChangeAsync(User user, string newEmail); Task ChangeEmailAsync(User user, string masterPassword, string newEmail, string newMasterPassword, string token, string key); @@ -28,7 +28,6 @@ namespace Bit.Core.Services Task UpdateKeyAsync(User user, string masterPassword, string key, string privateKey, IEnumerable ciphers, IEnumerable folders); Task RefreshSecurityStampAsync(User user, string masterPasswordHash); - Task SetupTwoFactorAsync(User user, TwoFactorProviderType provider); Task UpdateTwoFactorProviderAsync(User user, TwoFactorProviderType type); Task DisableTwoFactorProviderAsync(User user, TwoFactorProviderType type); Task RecoverTwoFactorAsync(string email, string masterPassword, string recoveryCode); diff --git a/src/Core/Services/Implementations/UserService.cs b/src/Core/Services/Implementations/UserService.cs index 1f36e9cfce..5a52e3a8df 100644 --- a/src/Core/Services/Implementations/UserService.cs +++ b/src/Core/Services/Implementations/UserService.cs @@ -182,43 +182,29 @@ namespace Bit.Core.Services await _mailService.SendMasterPasswordHintEmailAsync(email, user.MasterPasswordHint); } - public async Task SendTwoFactorEmailAsync(User user, string email = null) + public async Task SendTwoFactorEmailAsync(User user) { - if(string.IsNullOrWhiteSpace(email)) + var provider = user.GetTwoFactorProvider(TwoFactorProviderType.Email); + if(provider == null || provider.MetaData == null || !provider.MetaData.ContainsKey("Email")) { - var provider = user.GetTwoFactorProvider(TwoFactorProviderType.Email); - if(provider != null && provider.MetaData != null && provider.MetaData.ContainsKey("Email")) - { - email = provider.MetaData["Email"]; - } + throw new ArgumentNullException("No email."); } - if(string.IsNullOrWhiteSpace(email)) - { - throw new ArgumentNullException(nameof(email)); - } - - var token = await base.GenerateUserTokenAsync(user, TokenOptions.DefaultEmailProvider, "2faEmail:" + email); - await _mailService.SendChangeEmailEmailAsync(email, token); + var token = await base.GenerateUserTokenAsync(user, TokenOptions.DefaultEmailProvider, + "2faEmail:" + provider.MetaData["Email"]); + await _mailService.SendChangeEmailEmailAsync(provider.MetaData["Email"], token); } - public async Task VerifyTwoFactorEmailAsync(User user, string token, string email = null) + public async Task VerifyTwoFactorEmailAsync(User user, string token) { - if(string.IsNullOrWhiteSpace(email)) + var provider = user.GetTwoFactorProvider(TwoFactorProviderType.Email); + if(provider == null || provider.MetaData == null || !provider.MetaData.ContainsKey("Email")) { - var provider = user.GetTwoFactorProvider(TwoFactorProviderType.Email); - if(provider != null && provider.MetaData != null && provider.MetaData.ContainsKey("Email")) - { - email = provider.MetaData["Email"]; - } + throw new ArgumentNullException("No email."); } - if(string.IsNullOrWhiteSpace(email)) - { - throw new ArgumentNullException(nameof(email)); - } - - return await base.VerifyUserTokenAsync(user, TokenOptions.DefaultEmailProvider, "2faEmail:" + email, token); + return await base.VerifyUserTokenAsync(user, TokenOptions.DefaultEmailProvider, + "2faEmail:" + provider.MetaData["Email"], token); } public async Task InitiateEmailChangeAsync(User user, string newEmail) @@ -355,65 +341,6 @@ namespace Bit.Core.Services return IdentityResult.Failed(_identityErrorDescriber.PasswordMismatch()); } - public async Task SetupTwoFactorAsync(User user, TwoFactorProviderType provider) - { - var providers = user.GetTwoFactorProviders(); - if(providers != null && providers.ContainsKey(provider) && providers[provider].MetaData != null) - { - switch(provider) - { - case TwoFactorProviderType.Authenticator: - if(!string.IsNullOrWhiteSpace(providers[provider].MetaData["Key"])) - { - return; - } - break; - case TwoFactorProviderType.Email: - case TwoFactorProviderType.U2F: - case TwoFactorProviderType.YubiKey: - case TwoFactorProviderType.Duo: - break; - default: - throw new ArgumentException(nameof(provider)); - } - } - - if(providers == null) - { - providers = new Dictionary(); - } - - TwoFactorProvider providerInfo = null; - if(!providers.ContainsKey(provider)) - { - providerInfo = new TwoFactorProvider(); - providers.Add(provider, providerInfo); - } - else - { - providerInfo = providers[provider]; - } - - switch(provider) - { - case TwoFactorProviderType.Authenticator: - var key = KeyGeneration.GenerateRandomKey(20); - providerInfo.MetaData = new Dictionary { ["Key"] = Base32Encoding.ToString(key) }; - break; - case TwoFactorProviderType.Email: - case TwoFactorProviderType.U2F: - case TwoFactorProviderType.YubiKey: - case TwoFactorProviderType.Duo: - break; - default: - throw new ArgumentException(nameof(provider)); - } - - user.TwoFactorProvider = provider; - user.SetTwoFactorProviders(providers); - await SaveUserAsync(user); - } - public async Task UpdateTwoFactorProviderAsync(User user, TwoFactorProviderType type) { var providers = user.GetTwoFactorProviders();