1
0
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:
Vincent Salucci
2022-03-02 15:45:00 -06:00
committed by GitHub
parent 7bdb07da93
commit 19d5817f8f
30 changed files with 3669 additions and 19 deletions

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

@ -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;

View File

@ -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);
}
}
}

View File

@ -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);

View File

@ -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);
}
}
}