a9986fbc4c
Validators (Pushes and Pull Requests) / validate-ui-and-storage (push) Successful in 22s
Prepare Release / Build & Create Release (push) Successful in 59s
Prepare Release / Release Notification (push) Successful in 1s
Validators (Pushes and Pull Requests) / validate-core (push) Successful in 2m1s
72 lines
2.2 KiB
C#
72 lines
2.2 KiB
C#
namespace QuantEngine.Web.Services;
|
|
|
|
public interface IIpLockoutService
|
|
{
|
|
bool IsLockedOut(string ipAddress);
|
|
void RecordFailedAttempt(string ipAddress);
|
|
void ClearFailedAttempts(string ipAddress);
|
|
}
|
|
|
|
public class IpLockoutService : IIpLockoutService
|
|
{
|
|
private readonly Dictionary<string, (int Attempts, int LockoutCount, DateTime LockedUntil)> _attemptLog = [];
|
|
private const int MaxFailedAttempts = 3;
|
|
private const int LockoutDurationMinutes = 15;
|
|
private const int MaxLockoutDurationMinutes = 1440; // 24 hours cap
|
|
private readonly object _lock = new();
|
|
|
|
public bool IsLockedOut(string ipAddress)
|
|
{
|
|
lock (_lock)
|
|
{
|
|
if (_attemptLog.TryGetValue(ipAddress, out var record))
|
|
{
|
|
if (DateTime.UtcNow < record.LockedUntil)
|
|
return true;
|
|
|
|
// Reset failed attempts but keep the LockoutCount to calculate progressive delay on next failure
|
|
if (record.Attempts >= MaxFailedAttempts)
|
|
{
|
|
_attemptLog[ipAddress] = (0, record.LockoutCount, DateTime.MinValue);
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
}
|
|
|
|
public void RecordFailedAttempt(string ipAddress)
|
|
{
|
|
lock (_lock)
|
|
{
|
|
if (_attemptLog.TryGetValue(ipAddress, out var record))
|
|
{
|
|
record.Attempts++;
|
|
if (record.Attempts >= MaxFailedAttempts)
|
|
{
|
|
// Calculate exponential progressive lockout duration: 15 * 2^LockoutCount
|
|
var durationMinutes = LockoutDurationMinutes * Math.Pow(2, record.LockoutCount);
|
|
durationMinutes = Math.Min(durationMinutes, MaxLockoutDurationMinutes);
|
|
|
|
record.LockedUntil = DateTime.UtcNow.AddMinutes(durationMinutes);
|
|
record.LockoutCount++;
|
|
}
|
|
|
|
_attemptLog[ipAddress] = record;
|
|
}
|
|
else
|
|
{
|
|
_attemptLog[ipAddress] = (1, 0, DateTime.MinValue);
|
|
}
|
|
}
|
|
}
|
|
|
|
public void ClearFailedAttempts(string ipAddress)
|
|
{
|
|
lock (_lock)
|
|
{
|
|
_attemptLog.Remove(ipAddress);
|
|
}
|
|
}
|
|
}
|