diff --git a/src/Api/Controllers/TwoFactorController.cs b/src/Api/Controllers/TwoFactorController.cs index d252b949c..6adc60a03 100644 --- a/src/Api/Controllers/TwoFactorController.cs +++ b/src/Api/Controllers/TwoFactorController.cs @@ -67,6 +67,51 @@ namespace Bit.Api.Controllers return response; } + [HttpPost("get-yubikey")] + public async Task GetYubiKey([FromBody]TwoFactorRequestModel model) + { + var user = await CheckPasswordAsync(model.MasterPasswordHash); + var response = new TwoFactorYubiKeyResponseModel(user); + return response; + } + + [HttpPut("yubikey")] + [HttpPost("yubikey")] + public async Task PutYubiKey( + [FromBody]UpdateTwoFactorYubicoOtpRequestModel model) + { + var user = await CheckPasswordAsync(model.MasterPasswordHash); + model.ToUser(user); + + await ValidateYubiKeyAsync(user, nameof(model.Key1), model.Key1); + await ValidateYubiKeyAsync(user, nameof(model.Key2), model.Key2); + await ValidateYubiKeyAsync(user, nameof(model.Key3), model.Key3); + await ValidateYubiKeyAsync(user, nameof(model.Key4), model.Key4); + await ValidateYubiKeyAsync(user, nameof(model.Key5), model.Key5); + + await _userService.UpdateTwoFactorProviderAsync(user, TwoFactorProviderType.YubiKey); + var response = new TwoFactorYubiKeyResponseModel(user); + return response; + } + + public async Task ValidateYubiKeyAsync(User user, string name, string value) + { + if(string.IsNullOrWhiteSpace(value) || value.Length == 12) + { + return; + } + + if(!await _userManager.VerifyTwoFactorTokenAsync(user, TwoFactorProviderType.YubiKey.ToString(), value)) + { + await Task.Delay(2000); + throw new BadRequestException(name, $"{name} is invalid."); + } + else + { + await Task.Delay(500); + } + } + [HttpPost("get-email")] public async Task GetEmail([FromBody]TwoFactorRequestModel model) { @@ -74,7 +119,7 @@ namespace Bit.Api.Controllers var response = new TwoFactorEmailResponseModel(user); return response; } - + [HttpPost("send-email")] public async Task SendEmail([FromBody]TwoFactorEmailRequestModel model) { @@ -136,7 +181,7 @@ namespace Bit.Api.Controllers await Task.Delay(2000); throw new BadRequestException("MasterPasswordHash", "Invalid password."); } - + return user; } } diff --git a/src/Core/Identity/YubicoOtpTokenProvider.cs b/src/Core/Identity/YubicoOtpTokenProvider.cs index 4ff9d6085..3c14b694a 100644 --- a/src/Core/Identity/YubicoOtpTokenProvider.cs +++ b/src/Core/Identity/YubicoOtpTokenProvider.cs @@ -32,7 +32,7 @@ namespace Bit.Core.Identity public Task ValidateAsync(string purpose, string token, UserManager manager, User user) { - if(token.Length != 44) + if(string.IsNullOrWhiteSpace(token) || token.Length != 44) { return Task.FromResult(false); } @@ -45,7 +45,7 @@ namespace Bit.Core.Identity return Task.FromResult(false); } - var client = new YubicoClient(_globalSettings.Yubico.ClientId, _globalSettings.Yubico.ClientId); + var client = new YubicoClient(_globalSettings.Yubico.ClientId, _globalSettings.Yubico.Key); var response = client.Verify(token); return Task.FromResult(response.Status == YubicoResponseStatus.Ok); } diff --git a/src/Core/Models/Api/Request/TwoFactorRequestModels.cs b/src/Core/Models/Api/Request/TwoFactorRequestModels.cs index a26b251d1..2ff139a7a 100644 --- a/src/Core/Models/Api/Request/TwoFactorRequestModels.cs +++ b/src/Core/Models/Api/Request/TwoFactorRequestModels.cs @@ -57,12 +57,75 @@ namespace Bit.Core.Models.Api public string Key4 { get; set; } public string Key5 { get; set; } + public User ToUser(User extistingUser) + { + var providers = extistingUser.GetTwoFactorProviders(); + if(providers == null) + { + providers = new Dictionary(); + } + else if(providers.ContainsKey(TwoFactorProviderType.YubiKey)) + { + providers.Remove(TwoFactorProviderType.YubiKey); + } + + providers.Add(TwoFactorProviderType.YubiKey, new TwoFactorProvider + { + MetaData = new Dictionary + { + ["Key1"] = FormatKey(Key1), + ["Key2"] = FormatKey(Key2), + ["Key3"] = FormatKey(Key3), + ["Key4"] = FormatKey(Key4), + ["Key5"] = FormatKey(Key5) + }, + Enabled = true + }); + extistingUser.SetTwoFactorProviders(providers); + return extistingUser; + } + + private string FormatKey(string keyValue) + { + if(string.IsNullOrWhiteSpace(keyValue)) + { + return null; + } + + return keyValue.Substring(0, 12); + } + public IEnumerable Validate(ValidationContext validationContext) { if(string.IsNullOrWhiteSpace(Key1) && string.IsNullOrWhiteSpace(Key2) && string.IsNullOrWhiteSpace(Key3) && string.IsNullOrWhiteSpace(Key4) && string.IsNullOrWhiteSpace(Key5)) { - yield return new ValidationResult("A Key is required.", new string[] { nameof(Key1) }); + yield return new ValidationResult("A key is required.", new string[] { nameof(Key1) }); + } + + if(!string.IsNullOrWhiteSpace(Key1) && Key1.Length < 12) + { + yield return new ValidationResult("Key 1 in invalid.", new string[] { nameof(Key1) }); + } + + if(!string.IsNullOrWhiteSpace(Key2) && Key2.Length < 12) + { + yield return new ValidationResult("Key 2 in invalid.", new string[] { nameof(Key2) }); + } + + if(!string.IsNullOrWhiteSpace(Key3) && Key3.Length < 12) + { + yield return new ValidationResult("Key 3 in invalid.", new string[] { nameof(Key3) }); + } + + if(!string.IsNullOrWhiteSpace(Key4) && Key4.Length < 12) + { + yield return new ValidationResult("Key 4 in invalid.", new string[] { nameof(Key4) }); + } + + if(!string.IsNullOrWhiteSpace(Key5) && Key5.Length < 12) + { + yield return new ValidationResult("Key 5 in invalid.", new string[] { nameof(Key5) }); } } } diff --git a/src/Core/Models/Api/Response/TwoFactor/TwoFactorYubiKeyResponseModel.cs b/src/Core/Models/Api/Response/TwoFactor/TwoFactorYubiKeyResponseModel.cs index c2332aa59..bd87945c0 100644 --- a/src/Core/Models/Api/Response/TwoFactor/TwoFactorYubiKeyResponseModel.cs +++ b/src/Core/Models/Api/Response/TwoFactor/TwoFactorYubiKeyResponseModel.cs @@ -14,7 +14,7 @@ namespace Bit.Core.Models.Api throw new ArgumentNullException(nameof(user)); } - var provider = user.GetTwoFactorProvider(TwoFactorProviderType.Email); + var provider = user.GetTwoFactorProvider(TwoFactorProviderType.YubiKey); if(provider?.MetaData != null && provider.MetaData.Count > 0) { Enabled = provider.Enabled; @@ -29,7 +29,7 @@ namespace Bit.Core.Models.Api } if(provider.MetaData.ContainsKey("Key3")) { - Key1 = provider.MetaData["Key3"]; + Key3 = provider.MetaData["Key3"]; } if(provider.MetaData.ContainsKey("Key4")) {