diff --git a/src/Api/Controllers/AccountsController.cs b/src/Api/Controllers/AccountsController.cs index 9740a3cda1..4318d21cc5 100644 --- a/src/Api/Controllers/AccountsController.cs +++ b/src/Api/Controllers/AccountsController.cs @@ -209,30 +209,15 @@ namespace Bit.Api.Controllers return response; } - [HttpPut("two-factor-recover")] [HttpPost("two-factor-recover")] - public async Task PutTwoFactorRecover([FromBody]RecoverTwoFactorRequestModel model) + [AllowAnonymous] + public async Task PostTwoFactorRecover([FromBody]RecoverTwoFactorRequestModel model) { - var user = _currentContext.User; - if(!await _userManager.CheckPasswordAsync(user, model.MasterPasswordHash)) + if(!await _userService.RecoverTwoFactorAsync(model.Email, model.MasterPasswordHash, model.RecoveryCode)) { await Task.Delay(2000); - throw new BadRequestException("MasterPasswordHash", "Invalid password."); + throw new BadRequestException(string.Empty, "Invalid information. Try again."); } - - if(string.Compare(user.TwoFactorRecoveryCode, model.RecoveryCode, true) != 0) - { - await Task.Delay(2000); - throw new BadRequestException("RecoveryCode", "Invalid recovery code."); - } - - user.TwoFactorProvider = TwoFactorProvider.Authenticator; - user.TwoFactorEnabled = false; - user.TwoFactorRecoveryCode = null; - await _userService.SaveUserAsync(user); - - var response = new TwoFactorResponseModel(user); - return response; } [HttpPut("two-factor-regenerate")] diff --git a/src/Api/Models/Request/Accounts/RecoverTwoFactorRequestModel.cs b/src/Api/Models/Request/Accounts/RecoverTwoFactorRequestModel.cs index 86faf3db01..addf94a55d 100644 --- a/src/Api/Models/Request/Accounts/RecoverTwoFactorRequestModel.cs +++ b/src/Api/Models/Request/Accounts/RecoverTwoFactorRequestModel.cs @@ -5,6 +5,10 @@ namespace Bit.Api.Models { public class RecoverTwoFactorRequestModel { + [Required] + [EmailAddress] + [StringLength(50)] + public string Email { get; set; } [Required] public string MasterPasswordHash { get; set; } [Required] diff --git a/src/Core/Services/IUserService.cs b/src/Core/Services/IUserService.cs index 3e45c78fa3..d277fb60e8 100644 --- a/src/Core/Services/IUserService.cs +++ b/src/Core/Services/IUserService.cs @@ -17,6 +17,7 @@ namespace Bit.Core.Services Task ChangePasswordAsync(User user, string currentMasterPasswordHash, string newMasterPasswordHash, IEnumerable ciphers); Task RefreshSecurityStampAsync(User user, string masterPasswordHash); Task GetTwoFactorAsync(User user, Enums.TwoFactorProvider provider); + Task RecoverTwoFactorAsync(string email, string masterPassword, string recoveryCode); Task DeleteAsync(User user); } } diff --git a/src/Core/Services/UserService.cs b/src/Core/Services/UserService.cs index 209e0c406c..f5195e61db 100644 --- a/src/Core/Services/UserService.cs +++ b/src/Core/Services/UserService.cs @@ -10,6 +10,7 @@ using OtpSharp; using Base32; using System.Linq; using Microsoft.AspNetCore.Builder; +using Bit.Core.Enums; namespace Bit.Core.Services { @@ -249,6 +250,33 @@ namespace Bit.Core.Services await SaveUserAsync(user); } + public async Task RecoverTwoFactorAsync(string email, string masterPassword, string recoveryCode) + { + var user = await _userRepository.GetByEmailAsync(email); + if(user == null) + { + // No user exists. Do we want to send an email telling them this in the future? + return false; + } + + if(!await base.CheckPasswordAsync(user, masterPassword)) + { + return false; + } + + if(string.Compare(user.TwoFactorRecoveryCode, recoveryCode, true) != 0) + { + return false; + } + + user.TwoFactorProvider = TwoFactorProvider.Authenticator; + user.TwoFactorEnabled = false; + user.TwoFactorRecoveryCode = null; + await SaveUserAsync(user); + + return true; + } + private async Task UpdatePasswordHash(User user, string newPassword, bool validatePassword = true) { if(validatePassword)