feat(security): LoginThrottle — per-username+IP LDAP-bind failure lockout (arch-review P1)
Claude-Session: https://claude.ai/code/session_01MtdgwpEeCUn6cUA5f1LMPj
This commit is contained in:
@@ -104,6 +104,26 @@ public class SecurityOptions
|
||||
/// <see cref="DefaultCookieName"/>. Changing this invalidates existing sessions on next deploy.
|
||||
/// </summary>
|
||||
public string CookieName { get; set; } = DefaultCookieName;
|
||||
|
||||
/// <summary>
|
||||
/// Maximum failed LDAP binds tolerated per <see cref="LoginThrottle"/> fixed window for a
|
||||
/// single <c>{username}|{ip}</c> pair before the pair is locked out. Default: <b>5</b>.
|
||||
/// Set to <b>0</b> to disable login throttling entirely.
|
||||
/// </summary>
|
||||
public int MaxLoginFailuresPerWindow { get; set; } = 5;
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="LoginThrottle"/> fixed-window length in minutes. Failures older than this
|
||||
/// (measured from the window's first failure) no longer count toward the lockout threshold.
|
||||
/// Default: <b>5</b>.
|
||||
/// </summary>
|
||||
public int LoginFailureWindowMinutes { get; set; } = 5;
|
||||
|
||||
/// <summary>
|
||||
/// How long, in minutes, a <c>{username}|{ip}</c> pair stays locked out once it reaches
|
||||
/// <see cref="MaxLoginFailuresPerWindow"/> failures within the window. Default: <b>5</b>.
|
||||
/// </summary>
|
||||
public int LoginLockoutMinutes { get; set; } = 5;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -137,6 +157,32 @@ public sealed class SecurityOptionsValidator : Microsoft.Extensions.Options.IVal
|
||||
$"enforcement is defeated.");
|
||||
}
|
||||
|
||||
// SECURITY: login-throttle thresholds must be non-negative. 0 for
|
||||
// MaxLoginFailuresPerWindow is the documented "disable throttling" escape hatch;
|
||||
// a negative value (or a negative window/lockout) is a misconfiguration that would
|
||||
// make the fixed-window math ill-defined, so it fails fast at boot.
|
||||
if (options.MaxLoginFailuresPerWindow < 0)
|
||||
{
|
||||
return Microsoft.Extensions.Options.ValidateOptionsResult.Fail(
|
||||
$"{nameof(SecurityOptions.MaxLoginFailuresPerWindow)} " +
|
||||
$"({options.MaxLoginFailuresPerWindow}) must be non-negative " +
|
||||
$"(0 disables login throttling).");
|
||||
}
|
||||
|
||||
if (options.LoginFailureWindowMinutes < 0)
|
||||
{
|
||||
return Microsoft.Extensions.Options.ValidateOptionsResult.Fail(
|
||||
$"{nameof(SecurityOptions.LoginFailureWindowMinutes)} " +
|
||||
$"({options.LoginFailureWindowMinutes}) must be non-negative.");
|
||||
}
|
||||
|
||||
if (options.LoginLockoutMinutes < 0)
|
||||
{
|
||||
return Microsoft.Extensions.Options.ValidateOptionsResult.Fail(
|
||||
$"{nameof(SecurityOptions.LoginLockoutMinutes)} " +
|
||||
$"({options.LoginLockoutMinutes}) must be non-negative.");
|
||||
}
|
||||
|
||||
return Microsoft.Extensions.Options.ValidateOptionsResult.Success;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user