diff --git a/src/Api/Controllers/HibpController.cs b/src/Api/Controllers/HibpController.cs new file mode 100644 index 0000000000..41a4964f44 --- /dev/null +++ b/src/Api/Controllers/HibpController.cs @@ -0,0 +1,78 @@ +using System; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Authorization; +using System.Net.Http; +using System.Security.Cryptography; +using Bit.Core.Services; +using Bit.Core; +using System.Net; +using Bit.Core.Exceptions; + +namespace Bit.Api.Controllers +{ + [Route("hibp")] + [Authorize("Application")] + public class HibpController : Controller + { + private const string HibpBreachApi = "https://haveibeenpwned.com/api/v2/breachedaccount/{0}"; + private static HttpClient _httpClient; + + private readonly IUserService _userService; + private readonly CurrentContext _currentContext; + private readonly GlobalSettings _globalSettings; + + static HibpController() + { + _httpClient = new HttpClient(); + _httpClient.DefaultRequestHeaders.Add("User-Agent", "Bitwarden"); + } + + public HibpController( + IUserService userService, + CurrentContext currentContext, + GlobalSettings globalSettings) + { + _userService = userService; + _currentContext = currentContext; + _globalSettings = globalSettings; + } + + [HttpGet("breach")] + public async Task Get(string email) + { + var encodedEmail = WebUtility.UrlEncode(email); + var request = new HttpRequestMessage(HttpMethod.Get, string.Format(HibpBreachApi, encodedEmail)); + if(!string.IsNullOrWhiteSpace(_globalSettings.HibpBreachApiKey)) + { + request.Headers.Add("Authorization", $"Basic {_globalSettings.HibpBreachApiKey}"); + } + request.Headers.Add("Client-Id", GetClientId()); + request.Headers.Add("Client-Ip", _currentContext.IpAddress); + var response = await _httpClient.SendAsync(request); + if(response.IsSuccessStatusCode) + { + var data = await response.Content.ReadAsStringAsync(); + return Content(data, "application/json"); + } + else if(response.StatusCode == HttpStatusCode.NotFound) + { + return new NotFoundResult(); + } + else + { + throw new BadRequestException("Request failed. Status code: " + response.StatusCode); + } + } + + private string GetClientId() + { + var userId = _userService.GetProperUserId(User).Value; + using(var sha256 = SHA256.Create()) + { + var hash = sha256.ComputeHash(userId.ToByteArray()); + return Convert.ToBase64String(hash); + } + } + } +} diff --git a/src/Api/appsettings.json b/src/Api/appsettings.json index ad899e0d54..799dac66e1 100644 --- a/src/Api/appsettings.json +++ b/src/Api/appsettings.json @@ -174,6 +174,11 @@ "Period": "1m", "Limit": 5 }, + { + "Endpoint": "get:/hibp/breach", + "Period": "2s", + "Limit": 1 + }, { "Endpoint": "post:/installations", "Period": "2m", diff --git a/src/Core/GlobalSettings.cs b/src/Core/GlobalSettings.cs index 9412e2ee99..0640671c0c 100644 --- a/src/Core/GlobalSettings.cs +++ b/src/Core/GlobalSettings.cs @@ -12,6 +12,7 @@ namespace Bit.Core public virtual string LicenseDirectory { get; set; } public virtual string PushRelayBaseUri { get; set; } public virtual string InternalIdentityKey { get; set; } + public virtual string HibpBreachApiKey { get; set; } public virtual bool DisableUserRegistration { get; set; } public virtual InstallationSettings Installation { get; set; } = new InstallationSettings(); public virtual BaseServiceUriSettings BaseServiceUri { get; set; } = new BaseServiceUriSettings();