mirror of
https://github.com/bitwarden/server.git
synced 2025-07-04 17:42:49 -05:00
captcha scores (#1967)
* captcha scores * some api fixes * check bot on captcha attribute * Update src/Core/Services/Implementations/HCaptchaValidationService.cs Co-authored-by: e271828- <e271828-@users.noreply.github.com> Co-authored-by: Chad Scharf <3904944+cscharf@users.noreply.github.com> Co-authored-by: e271828- <e271828-@users.noreply.github.com>
This commit is contained in:
@ -12,6 +12,7 @@ using Bit.Core.Enums;
|
||||
using Bit.Core.Identity;
|
||||
using Bit.Core.Models;
|
||||
using Bit.Core.Models.Api;
|
||||
using Bit.Core.Models.Business;
|
||||
using Bit.Core.Models.Data;
|
||||
using Bit.Core.Repositories;
|
||||
using Bit.Core.Services;
|
||||
@ -78,18 +79,25 @@ namespace Bit.Core.IdentityServer
|
||||
_captchaValidationService = captchaValidationService;
|
||||
}
|
||||
|
||||
protected async Task ValidateAsync(T context, ValidatedTokenRequest request, bool unknownDevice = false)
|
||||
protected async Task ValidateAsync(T context, ValidatedTokenRequest request,
|
||||
CustomValidatorRequestContext validatorContext)
|
||||
{
|
||||
var isBot = (validatorContext.CaptchaResponse?.IsBot ?? false);
|
||||
|
||||
var twoFactorToken = request.Raw["TwoFactorToken"]?.ToString();
|
||||
var twoFactorProvider = request.Raw["TwoFactorProvider"]?.ToString();
|
||||
var twoFactorRemember = request.Raw["TwoFactorRemember"]?.ToString() == "1";
|
||||
var twoFactorRequest = !string.IsNullOrWhiteSpace(twoFactorToken) &&
|
||||
!string.IsNullOrWhiteSpace(twoFactorProvider);
|
||||
|
||||
var (user, valid) = await ValidateContextAsync(context);
|
||||
var valid = await ValidateContextAsync(context, validatorContext);
|
||||
var user = validatorContext.User;
|
||||
if (!valid)
|
||||
{
|
||||
await UpdateFailedAuthDetailsAsync(user, false, unknownDevice);
|
||||
await UpdateFailedAuthDetailsAsync(user, false, !validatorContext.KnownDevice);
|
||||
}
|
||||
if (!valid || isBot)
|
||||
{
|
||||
await BuildErrorResultAsync("Username or password is incorrect. Try again.", false, context, user);
|
||||
return;
|
||||
}
|
||||
@ -112,13 +120,13 @@ namespace Bit.Core.IdentityServer
|
||||
|
||||
AfterVerifyTwoFactor(user, twoFactorProviderType, requires2FABecauseNewDevice);
|
||||
|
||||
if (!verified && twoFactorProviderType != TwoFactorProviderType.Remember)
|
||||
if ((!verified || isBot) && twoFactorProviderType != TwoFactorProviderType.Remember)
|
||||
{
|
||||
await UpdateFailedAuthDetailsAsync(user, true, unknownDevice);
|
||||
await UpdateFailedAuthDetailsAsync(user, true, !validatorContext.KnownDevice);
|
||||
await BuildErrorResultAsync("Two-step token is invalid. Try again.", true, context, user);
|
||||
return;
|
||||
}
|
||||
else if (!verified && twoFactorProviderType == TwoFactorProviderType.Remember)
|
||||
else if ((!verified || isBot) && twoFactorProviderType == TwoFactorProviderType.Remember)
|
||||
{
|
||||
// Delay for brute force.
|
||||
await Task.Delay(2000);
|
||||
@ -153,7 +161,7 @@ namespace Bit.Core.IdentityServer
|
||||
}
|
||||
}
|
||||
|
||||
protected abstract Task<(User, bool)> ValidateContextAsync(T context);
|
||||
protected abstract Task<bool> ValidateContextAsync(T context, CustomValidatorRequestContext validatorContext);
|
||||
|
||||
protected async Task BuildSuccessResultAsync(User user, T context, Device device, bool sendRememberToken)
|
||||
{
|
||||
@ -407,9 +415,7 @@ namespace Bit.Core.IdentityServer
|
||||
|
||||
private void BeforeVerifyTwoFactor(User user, TwoFactorProviderType type, bool requires2FABecauseNewDevice)
|
||||
{
|
||||
if (type == TwoFactorProviderType.Email
|
||||
&&
|
||||
requires2FABecauseNewDevice)
|
||||
if (type == TwoFactorProviderType.Email && requires2FABecauseNewDevice)
|
||||
{
|
||||
user.SetTwoFactorProviders(new Dictionary<TwoFactorProviderType, TwoFactorProvider>
|
||||
{
|
||||
@ -424,9 +430,7 @@ namespace Bit.Core.IdentityServer
|
||||
|
||||
private void AfterVerifyTwoFactor(User user, TwoFactorProviderType type, bool requires2FABecauseNewDevice)
|
||||
{
|
||||
if (type == TwoFactorProviderType.Email
|
||||
&&
|
||||
requires2FABecauseNewDevice)
|
||||
if (type == TwoFactorProviderType.Email && requires2FABecauseNewDevice)
|
||||
{
|
||||
user.ClearTwoFactorProviders();
|
||||
}
|
||||
@ -595,7 +599,7 @@ namespace Bit.Core.IdentityServer
|
||||
user.LastFailedLoginDate = user.RevisionDate = utcNow;
|
||||
await _userRepository.ReplaceAsync(user);
|
||||
|
||||
if (_captchaValidationService.ValidateFailedAuthEmailConditions(unknownDevice, user.FailedLoginCount))
|
||||
if (ValidateFailedAuthEmailConditions(unknownDevice, user))
|
||||
{
|
||||
if (twoFactorInvalid)
|
||||
{
|
||||
@ -607,5 +611,12 @@ namespace Bit.Core.IdentityServer
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private bool ValidateFailedAuthEmailConditions(bool unknownDevice, User user)
|
||||
{
|
||||
var failedLoginCeiling = _globalSettings.Captcha.MaximumFailedLoginAttempts;
|
||||
var failedLoginCount = user?.FailedLoginCount ?? 0;
|
||||
return unknownDevice && failedLoginCeiling > 0 && failedLoginCount == failedLoginCeiling;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user