mirror of
https://github.com/bitwarden/server.git
synced 2025-07-02 16:42:50 -05:00
Feature/sync Enable hcaptcha on login (#1469)
* Share globalSettings hcaptcha public key with clients * Require captcha valid only prior to two factor users with two factor will have already solved captcha is necessary. Users without two factor will have`TwoFactorVerified` set to false * Do not require CaptchaResponse on two-factor requests * Add option to always require captcha for testing purposes * Allow for self-hosted instances if they want to use it * Move refresh suggestion to correct error * Expect lifetime in helper method * Add captcha bypass token to successful captcha validations * Remove twofactorValidated * PR Feedback
This commit is contained in:
@ -1,10 +1,15 @@
|
||||
using System.Threading.Tasks;
|
||||
using Bit.Core.Models.Table;
|
||||
|
||||
namespace Bit.Core.Services
|
||||
{
|
||||
public interface ICaptchaValidationService
|
||||
{
|
||||
bool ServiceEnabled { get; }
|
||||
string SiteKey { get; }
|
||||
bool RequireCaptcha { get; }
|
||||
Task<bool> ValidateCaptchaResponseAsync(string captchResponse, string clientIpAddress);
|
||||
string GenerateCaptchaBypassToken(User user);
|
||||
bool ValidateCaptchaBypassToken(string encryptedToken, User user);
|
||||
}
|
||||
}
|
||||
|
@ -115,7 +115,8 @@ namespace Bit.Core.Services
|
||||
throw new BadRequestException("Emergency Access not valid.");
|
||||
}
|
||||
|
||||
if (!CoreHelpers.TokenIsValid("EmergencyAccessInvite", _dataProtector, token, user.Email, emergencyAccessId, _globalSettings))
|
||||
if (!CoreHelpers.TokenIsValid("EmergencyAccessInvite", _dataProtector, token, user.Email, emergencyAccessId,
|
||||
_globalSettings.OrganizationInviteExpirationHours))
|
||||
{
|
||||
throw new BadRequestException("Invalid token.");
|
||||
}
|
||||
|
@ -2,7 +2,10 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Net.Http;
|
||||
using System.Threading.Tasks;
|
||||
using Bit.Core.Models.Table;
|
||||
using Bit.Core.Settings;
|
||||
using Bit.Core.Utilities;
|
||||
using Microsoft.AspNetCore.DataProtection;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
@ -10,21 +13,36 @@ namespace Bit.Core.Services
|
||||
{
|
||||
public class HCaptchaValidationService : ICaptchaValidationService
|
||||
{
|
||||
private const double TokenLifetimeInHours = (double)5 / 60; // 5 minutes
|
||||
private const string TokenName = "CaptchaBypassToken";
|
||||
private const string TokenClearTextPrefix = "BWCaptchaBypass_";
|
||||
private readonly ILogger<HCaptchaValidationService> _logger;
|
||||
private readonly IHttpClientFactory _httpClientFactory;
|
||||
private readonly GlobalSettings _globalSettings;
|
||||
private readonly IDataProtector _dataProtector;
|
||||
|
||||
public HCaptchaValidationService(
|
||||
ILogger<HCaptchaValidationService> logger,
|
||||
IHttpClientFactory httpClientFactory,
|
||||
IDataProtectionProvider dataProtectorProvider,
|
||||
GlobalSettings globalSettings)
|
||||
{
|
||||
_logger = logger;
|
||||
_httpClientFactory = httpClientFactory;
|
||||
_globalSettings = globalSettings;
|
||||
_dataProtector = dataProtectorProvider.CreateProtector("CaptchaServiceDataProtector");
|
||||
}
|
||||
|
||||
public bool ServiceEnabled => true;
|
||||
public string SiteKey => _globalSettings.Captcha.HCaptchaSiteKey;
|
||||
public bool RequireCaptcha => _globalSettings.Captcha.RequireCaptcha;
|
||||
|
||||
public string GenerateCaptchaBypassToken(User user) =>
|
||||
$"{TokenClearTextPrefix}{_dataProtector.Protect(CaptchaBypassTokenContent(user))}";
|
||||
public bool ValidateCaptchaBypassToken(string encryptedToken, User user) =>
|
||||
encryptedToken.StartsWith(TokenClearTextPrefix) && user != null &&
|
||||
CoreHelpers.TokenIsValid(TokenName, _dataProtector, encryptedToken[TokenClearTextPrefix.Length..],
|
||||
user.Email, user.Id, TokenLifetimeInHours);
|
||||
|
||||
public async Task<bool> ValidateCaptchaResponseAsync(string captchResponse, string clientIpAddress)
|
||||
{
|
||||
@ -43,7 +61,7 @@ namespace Bit.Core.Services
|
||||
{
|
||||
{ "response", captchResponse.TrimStart("hcaptcha|".ToCharArray()) },
|
||||
{ "secret", _globalSettings.Captcha.HCaptchaSecretKey },
|
||||
{ "sitekey", _globalSettings.Captcha.HCaptchaSiteKey },
|
||||
{ "sitekey", SiteKey },
|
||||
{ "remoteip", clientIpAddress }
|
||||
})
|
||||
};
|
||||
@ -68,5 +86,13 @@ namespace Bit.Core.Services
|
||||
dynamic jsonResponse = JsonConvert.DeserializeObject(responseContent);
|
||||
return (bool)jsonResponse.success;
|
||||
}
|
||||
|
||||
private static string CaptchaBypassTokenContent(User user) =>
|
||||
string.Join(' ', new object[] {
|
||||
TokenName,
|
||||
user?.Id,
|
||||
user?.Email,
|
||||
CoreHelpers.ToEpocMilliseconds(DateTime.UtcNow.AddHours(TokenLifetimeInHours))
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -1,10 +1,16 @@
|
||||
using System.Threading.Tasks;
|
||||
using Bit.Core.Models.Table;
|
||||
|
||||
namespace Bit.Core.Services
|
||||
{
|
||||
public class NoopCaptchaValidationService : ICaptchaValidationService
|
||||
{
|
||||
public bool ServiceEnabled => false;
|
||||
public string SiteKey => null;
|
||||
public bool RequireCaptcha => false;
|
||||
|
||||
public string GenerateCaptchaBypassToken(User user) => "";
|
||||
public bool ValidateCaptchaBypassToken(string encryptedToken, User user) => false;
|
||||
|
||||
public Task<bool> ValidateCaptchaResponseAsync(string captchResponse, string clientIpAddress)
|
||||
{
|
||||
|
Reference in New Issue
Block a user