mirror of
https://github.com/bitwarden/server.git
synced 2025-07-02 00:22:50 -05:00
[Captcha] Implement failed logins ceiling (#1870)
* [Hacker1] Failed Login Attempts Captcha * [Captcha] Implement failed logins ceiling * Formatting * Updated approach after implementation talks with Kyle * Updated email templates // Updated calling arch for failed attempts * Formatting * Updated 2fa email links * Renamed baserequest methods to better match their actions * EF migrations/scripts * Updated with requested changes * Defaults for MaxiumumFailedLoginAttempts
This commit is contained in:
@ -8,9 +8,10 @@ namespace Bit.Core.Services
|
||||
{
|
||||
string SiteKey { get; }
|
||||
string SiteKeyResponseKeyName { get; }
|
||||
bool RequireCaptchaValidation(ICurrentContext currentContext);
|
||||
bool RequireCaptchaValidation(ICurrentContext currentContext, int? failedLoginCount = null);
|
||||
Task<bool> ValidateCaptchaResponseAsync(string captchResponse, string clientIpAddress);
|
||||
string GenerateCaptchaBypassToken(User user);
|
||||
bool ValidateCaptchaBypassToken(string encryptedToken, User user);
|
||||
bool ValidateFailedAuthEmailConditions(bool unknownDevice, int failedLoginCount);
|
||||
}
|
||||
}
|
||||
|
@ -53,5 +53,7 @@ namespace Bit.Core.Services
|
||||
Task SendFamiliesForEnterpriseRedeemedEmailsAsync(string familyUserEmail, string sponsorEmail);
|
||||
Task SendFamiliesForEnterpriseSponsorshipRevertingEmailAsync(string email, string familyOrgName);
|
||||
Task SendOTPEmailAsync(string email, string token);
|
||||
Task SendFailedLoginAttemptsEmailAsync(string email, DateTime utcNow, string ip);
|
||||
Task SendFailedTwoFactorAttemptsEmailAsync(string email, DateTime utcNow, string ip);
|
||||
}
|
||||
}
|
||||
|
@ -83,8 +83,19 @@ namespace Bit.Core.Services
|
||||
return root.GetProperty("success").GetBoolean();
|
||||
}
|
||||
|
||||
public bool RequireCaptchaValidation(ICurrentContext currentContext) =>
|
||||
currentContext.IsBot || _globalSettings.Captcha.ForceCaptchaRequired;
|
||||
public bool RequireCaptchaValidation(ICurrentContext currentContext, int? failedLoginCount = null)
|
||||
{
|
||||
var failedLoginCeiling = _globalSettings.Captcha.MaximumFailedLoginAttempts.GetValueOrDefault();
|
||||
return currentContext.IsBot ||
|
||||
_globalSettings.Captcha.ForceCaptchaRequired ||
|
||||
failedLoginCeiling > 0 && failedLoginCount.GetValueOrDefault() >= failedLoginCeiling;
|
||||
}
|
||||
|
||||
public bool ValidateFailedAuthEmailConditions(bool unknownDevice, int failedLoginCount)
|
||||
{
|
||||
var failedLoginCeiling = _globalSettings.Captcha.MaximumFailedLoginAttempts.GetValueOrDefault();
|
||||
return unknownDevice && failedLoginCeiling > 0 && failedLoginCount == failedLoginCeiling;
|
||||
}
|
||||
|
||||
private static bool TokenIsApiKey(string bypassToken, User user) =>
|
||||
!string.IsNullOrWhiteSpace(bypassToken) && user != null && user.ApiKey == bypassToken;
|
||||
|
@ -874,5 +874,39 @@ namespace Bit.Core.Services
|
||||
message.Category = "OTP";
|
||||
await _mailDeliveryService.SendEmailAsync(message);
|
||||
}
|
||||
|
||||
public async Task SendFailedLoginAttemptsEmailAsync(string email, DateTime utcNow, string ip)
|
||||
{
|
||||
var message = CreateDefaultMessage("Failed login attempts detected", email);
|
||||
var model = new FailedAuthAttemptsModel()
|
||||
{
|
||||
TheDate = utcNow.ToLongDateString(),
|
||||
TheTime = utcNow.ToShortTimeString(),
|
||||
TimeZone = "UTC",
|
||||
IpAddress = ip,
|
||||
AffectedEmail = email
|
||||
|
||||
};
|
||||
await AddMessageContentAsync(message, "FailedLoginAttempts", model);
|
||||
message.Category = "FailedLoginAttempts";
|
||||
await _mailDeliveryService.SendEmailAsync(message);
|
||||
}
|
||||
|
||||
public async Task SendFailedTwoFactorAttemptsEmailAsync(string email, DateTime utcNow, string ip)
|
||||
{
|
||||
var message = CreateDefaultMessage("Failed login attempts detected", email);
|
||||
var model = new FailedAuthAttemptsModel()
|
||||
{
|
||||
TheDate = utcNow.ToLongDateString(),
|
||||
TheTime = utcNow.ToShortTimeString(),
|
||||
TimeZone = "UTC",
|
||||
IpAddress = ip,
|
||||
AffectedEmail = email
|
||||
|
||||
};
|
||||
await AddMessageContentAsync(message, "FailedTwoFactorAttempts", model);
|
||||
message.Category = "FailedTwoFactorAttempts";
|
||||
await _mailDeliveryService.SendEmailAsync(message);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -8,11 +8,10 @@ namespace Bit.Core.Services
|
||||
{
|
||||
public string SiteKeyResponseKeyName => null;
|
||||
public string SiteKey => null;
|
||||
public bool RequireCaptchaValidation(ICurrentContext currentContext) => false;
|
||||
|
||||
public bool RequireCaptchaValidation(ICurrentContext currentContext, int? failedLoginCount) => false;
|
||||
public bool ValidateFailedAuthEmailConditions(bool unknownDevice, int failedLoginCount) => false;
|
||||
public string GenerateCaptchaBypassToken(User user) => "";
|
||||
public bool ValidateCaptchaBypassToken(string encryptedToken, User user) => false;
|
||||
|
||||
public Task<bool> ValidateCaptchaResponseAsync(string captchResponse, string clientIpAddress)
|
||||
{
|
||||
return Task.FromResult(true);
|
||||
|
@ -220,5 +220,15 @@ namespace Bit.Core.Services
|
||||
{
|
||||
return Task.FromResult(0);
|
||||
}
|
||||
|
||||
public Task SendFailedLoginAttemptsEmailAsync(string email, DateTime utcNow, string ip)
|
||||
{
|
||||
return Task.FromResult(0);
|
||||
}
|
||||
|
||||
public Task SendFailedTwoFactorAttemptsEmailAsync(string email, DateTime utcNow, string ip)
|
||||
{
|
||||
return Task.FromResult(0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user