diff --git a/src/Api/Auth/Controllers/TwoFactorController.cs b/src/Api/Auth/Controllers/TwoFactorController.cs index c3df892535..113b46aacb 100644 --- a/src/Api/Auth/Controllers/TwoFactorController.cs +++ b/src/Api/Auth/Controllers/TwoFactorController.cs @@ -42,6 +42,7 @@ public class TwoFactorController : Controller private readonly ITwoFactorEmailService _twoFactorEmailService; private readonly IStartTwoFactorWebAuthnRegistrationCommand _startTwoFactorWebAuthnRegistrationCommand; private readonly ICompleteTwoFactorWebAuthnRegistrationCommand _completeTwoFactorWebAuthnRegistrationCommand; + private readonly IDeleteTwoFactorWebAuthnCredentialCommand _deleteTwoFactorWebAuthnCredentialCommand; public TwoFactorController( IUserService userService, @@ -55,7 +56,8 @@ public class TwoFactorController : Controller IDataProtectorTokenFactory ssoEmailTwoFactorSessionDataProtector, ITwoFactorEmailService twoFactorEmailService, IStartTwoFactorWebAuthnRegistrationCommand startTwoFactorWebAuthnRegistrationCommand, - ICompleteTwoFactorWebAuthnRegistrationCommand completeTwoFactorWebAuthnRegistrationCommand) + ICompleteTwoFactorWebAuthnRegistrationCommand completeTwoFactorWebAuthnRegistrationCommand, + IDeleteTwoFactorWebAuthnCredentialCommand deleteTwoFactorWebAuthnCredentialCommand) { _userService = userService; _organizationRepository = organizationRepository; @@ -69,6 +71,7 @@ public class TwoFactorController : Controller _twoFactorEmailService = twoFactorEmailService; _startTwoFactorWebAuthnRegistrationCommand = startTwoFactorWebAuthnRegistrationCommand; _completeTwoFactorWebAuthnRegistrationCommand = completeTwoFactorWebAuthnRegistrationCommand; + _deleteTwoFactorWebAuthnCredentialCommand = deleteTwoFactorWebAuthnCredentialCommand; } [HttpGet("")] @@ -321,7 +324,18 @@ public class TwoFactorController : Controller [FromBody] TwoFactorWebAuthnDeleteRequestModel model) { var user = await CheckAsync(model, false); - await _userService.DeleteWebAuthnKeyAsync(user, model.Id.Value); + + if (!model.Id.HasValue) + { + throw new BadRequestException("Unable to delete WebAuthn credential."); + } + + var success = await _deleteTwoFactorWebAuthnCredentialCommand.DeleteTwoFactorWebAuthnCredentialAsync(user, model.Id.Value); + if (!success) + { + throw new BadRequestException("Unable to delete WebAuthn credential."); + } + var response = new TwoFactorWebAuthnResponseModel(user); return response; } diff --git a/src/Core/Auth/UserFeatures/TwoFactorAuth/IDeleteTwoFactorWebAuthnCredentialCommand.cs b/src/Core/Auth/UserFeatures/TwoFactorAuth/IDeleteTwoFactorWebAuthnCredentialCommand.cs new file mode 100644 index 0000000000..61774eb69e --- /dev/null +++ b/src/Core/Auth/UserFeatures/TwoFactorAuth/IDeleteTwoFactorWebAuthnCredentialCommand.cs @@ -0,0 +1,16 @@ +using Bit.Core.Entities; + +namespace Bit.Core.Auth.UserFeatures.TwoFactorAuth; + +public interface IDeleteTwoFactorWebAuthnCredentialCommand +{ + /// + /// Deletes a Two-factor WebAuthn credential by ID. + /// + /// The current user. + /// ID of the credential to delete. + /// Whether deletion was successful. + Task DeleteTwoFactorWebAuthnCredentialAsync(User user, int id); +} + + diff --git a/src/Core/Auth/UserFeatures/TwoFactorAuth/Implementations/DeleteTwoFactorWebAuthnCredentialCommand.cs b/src/Core/Auth/UserFeatures/TwoFactorAuth/Implementations/DeleteTwoFactorWebAuthnCredentialCommand.cs new file mode 100644 index 0000000000..0b572d04ed --- /dev/null +++ b/src/Core/Auth/UserFeatures/TwoFactorAuth/Implementations/DeleteTwoFactorWebAuthnCredentialCommand.cs @@ -0,0 +1,41 @@ +using Bit.Core.Auth.Enums; +using Bit.Core.Entities; +using Bit.Core.Services; + +namespace Bit.Core.Auth.UserFeatures.TwoFactorAuth.Implementations; + +public class DeleteTwoFactorWebAuthnCredentialCommand : IDeleteTwoFactorWebAuthnCredentialCommand +{ + private readonly IUserService _userService; + + public DeleteTwoFactorWebAuthnCredentialCommand(IUserService userService) + { + _userService = userService; + } + public async Task DeleteTwoFactorWebAuthnCredentialAsync(User user, int id) + { + var providers = user.GetTwoFactorProviders(); + if (providers == null) + { + return false; + } + + var keyName = $"Key{id}"; + var provider = user.GetTwoFactorProvider(TwoFactorProviderType.WebAuthn); + if (!provider?.MetaData?.ContainsKey(keyName) ?? true) + { + return false; + } + + if (provider.MetaData.Count < 2) + { + return false; + } + + provider.MetaData.Remove(keyName); + providers[TwoFactorProviderType.WebAuthn] = provider; + user.SetTwoFactorProviders(providers); + await _userService.UpdateTwoFactorProviderAsync(user, TwoFactorProviderType.WebAuthn); + return true; + } +} diff --git a/src/Core/Auth/UserFeatures/UserServiceCollectionExtensions.cs b/src/Core/Auth/UserFeatures/UserServiceCollectionExtensions.cs index 9e158422e9..407def39af 100644 --- a/src/Core/Auth/UserFeatures/UserServiceCollectionExtensions.cs +++ b/src/Core/Auth/UserFeatures/UserServiceCollectionExtensions.cs @@ -75,6 +75,7 @@ public static class UserServiceCollectionExtensions services .AddScoped(); + services.AddScoped(); services.AddScoped(); } diff --git a/src/Core/Services/IUserService.cs b/src/Core/Services/IUserService.cs index 6b6c6a2641..68392bb80c 100644 --- a/src/Core/Services/IUserService.cs +++ b/src/Core/Services/IUserService.cs @@ -23,7 +23,6 @@ public interface IUserService Task CreateUserAsync(User user); Task CreateUserAsync(User user, string masterPasswordHash); Task SendMasterPasswordHintAsync(string email); - Task DeleteWebAuthnKeyAsync(User user, int id); Task SendEmailVerificationAsync(User user); Task ConfirmEmailAsync(User user, string token); Task InitiateEmailChangeAsync(User user, string newEmail); diff --git a/src/Core/Services/Implementations/UserService.cs b/src/Core/Services/Implementations/UserService.cs index dd4188e1ea..861ca4bb3a 100644 --- a/src/Core/Services/Implementations/UserService.cs +++ b/src/Core/Services/Implementations/UserService.cs @@ -344,33 +344,6 @@ public class UserService : UserManager, IUserService await _mailService.SendMasterPasswordHintEmailAsync(email, user.MasterPasswordHint); } - public async Task DeleteWebAuthnKeyAsync(User user, int id) - { - var providers = user.GetTwoFactorProviders(); - if (providers == null) - { - return false; - } - - var keyName = $"Key{id}"; - var provider = user.GetTwoFactorProvider(TwoFactorProviderType.WebAuthn); - if (!provider?.MetaData?.ContainsKey(keyName) ?? true) - { - return false; - } - - if (provider.MetaData.Count < 2) - { - return false; - } - - provider.MetaData.Remove(keyName); - providers[TwoFactorProviderType.WebAuthn] = provider; - user.SetTwoFactorProviders(providers); - await UpdateTwoFactorProviderAsync(user, TwoFactorProviderType.WebAuthn); - return true; - } - public async Task SendEmailVerificationAsync(User user) { if (user.EmailVerified)