feat(web): implement progressive IP lockout duration scaling based on block counts
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

This commit is contained in:
2026-07-12 13:26:30 +09:00
parent ee14f5fbe4
commit a9986fbc4c
@@ -9,9 +9,10 @@ public interface IIpLockoutService
public class IpLockoutService : IIpLockoutService
{
private readonly Dictionary<string, (int Attempts, DateTime LockedUntil)> _attemptLog = [];
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)
@@ -23,7 +24,11 @@ public class IpLockoutService : IIpLockoutService
if (DateTime.UtcNow < record.LockedUntil)
return true;
_attemptLog.Remove(ipAddress);
// 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;
@@ -39,14 +44,19 @@ public class IpLockoutService : IIpLockoutService
record.Attempts++;
if (record.Attempts >= MaxFailedAttempts)
{
record.LockedUntil = DateTime.UtcNow.AddMinutes(LockoutDurationMinutes);
// 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, DateTime.UtcNow);
_attemptLog[ipAddress] = (1, 0, DateTime.MinValue);
}
}
}