From a9986fbc4cd92c22a35a849a7b90f7bfc698eeef Mon Sep 17 00:00:00 2001 From: kjh2064 Date: Sun, 12 Jul 2026 13:26:30 +0900 Subject: [PATCH] feat(web): implement progressive IP lockout duration scaling based on block counts --- .../Services/IIpLockoutService.cs | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/src/dotnet/QuantEngine.Web/Services/IIpLockoutService.cs b/src/dotnet/QuantEngine.Web/Services/IIpLockoutService.cs index 1d4c41f2..d59b218a 100644 --- a/src/dotnet/QuantEngine.Web/Services/IIpLockoutService.cs +++ b/src/dotnet/QuantEngine.Web/Services/IIpLockoutService.cs @@ -9,9 +9,10 @@ public interface IIpLockoutService public class IpLockoutService : IIpLockoutService { - private readonly Dictionary _attemptLog = []; + private readonly Dictionary _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); } } }