feat: Migrate admin UI from Blazor WASM/MudBlazor to Razor Pages/Cookie Auth/Tabler
- Remove QuantEngine.Web.Client from .sln (keep on disk for reference) - Replace Blazor Interactive WebAssembly with server-rendered Razor Pages - Implement Cookie Authentication (HttpOnly, SameSite=Lax, 12h expiry) - Add AuthService with BCrypt password hashing + auto-migration from SHA-256 - Implement IpLockoutService (3 strikes → 15-min ban) - Create Admin folder structure with Layout + shared partials - Implement Dashboard, Collection, Users index pages (base structure) - Remove hardcoded backdoors (master_recovery, dev auth bypass) - Remove hardcoded localhost:5265 URLs - Add Tabler UI base styling (Bootstrap 5 CDN + custom admin.css) - Update CLAUDE.md with new UI standards and auth policies - Build: 0 errors, 0 warnings (ready for dev testing) Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,61 @@
|
||||
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, DateTime LockedUntil)> _attemptLog = [];
|
||||
private const int MaxFailedAttempts = 3;
|
||||
private const int LockoutDurationMinutes = 15;
|
||||
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;
|
||||
|
||||
_attemptLog.Remove(ipAddress);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public void RecordFailedAttempt(string ipAddress)
|
||||
{
|
||||
lock (_lock)
|
||||
{
|
||||
if (_attemptLog.TryGetValue(ipAddress, out var record))
|
||||
{
|
||||
record.Attempts++;
|
||||
if (record.Attempts >= MaxFailedAttempts)
|
||||
{
|
||||
record.LockedUntil = DateTime.UtcNow.AddMinutes(LockoutDurationMinutes);
|
||||
}
|
||||
|
||||
_attemptLog[ipAddress] = record;
|
||||
}
|
||||
else
|
||||
{
|
||||
_attemptLog[ipAddress] = (1, DateTime.UtcNow);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void ClearFailedAttempts(string ipAddress)
|
||||
{
|
||||
lock (_lock)
|
||||
{
|
||||
_attemptLog.Remove(ipAddress);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user