pub struct LoginLimiter { /* private fields */ }Expand description
Fixed-window failed-login counter, keyed by client address.
In-process on purpose: it protects a listener that defaults to loopback and holds a handful of accounts, and a database-backed counter would add a write to the very path being flooded.
An attempt is counted when it starts, not when it fails. begin
reserves a slot under the lock and refuses once failures plus attempts
still in flight reach the limit. Counting only finished failures was a
check-then-act race: a burst of parallel requests from one address all
passed the check before the first of them had paid its 600 000 iterations
and recorded anything, so the burst bought as many guesses — and as much
KDF time — as it had requests. The slot is a LoginAttempt guard, so
every early return (a database error included) gives it back.
Implementations§
Source§impl LoginLimiter
impl LoginLimiter
Sourcepub fn new(max_attempts: u32, window_seconds: u64) -> Self
pub fn new(max_attempts: u32, window_seconds: u64) -> Self
At most max_attempts failures per client address (an IPv6 address by
its /64) within window_seconds.
Sourcepub fn rebuilt(&self, max_attempts: u32, window_seconds: u64) -> Self
pub fn rebuilt(&self, max_attempts: u32, window_seconds: u64) -> Self
The same counters under new limits.
A configuration reload rebuilds the admin router, and with it every value
AdminState derives from [admin] — which for this type would mean
starting from an empty map. That is a security regression, not a cosmetic
one: a reload in the middle of a brute-force attempt would clear the
attacker’s backoff, and admin.login_* is exactly the sort of key an
operator edits because they are being flooded.
Carrying the whole limiter across instead would be the other error,
leaving login_max_attempts and login_window_seconds silently stale.
So the counters move and the limits do not.
The in-flight counts do not move: their guards hold the old limiter and settle against it, so a count copied here would never be released. The cost is that an attempt straddling a reload is not counted, once.
Sourcepub fn begin(&self, client: Option<IpAddr>) -> Result<LoginAttempt<'_>, u64>
pub fn begin(&self, client: Option<IpAddr>) -> Result<LoginAttempt<'_>, u64>
Starts a login attempt from this address, or refuses it. Err carries
the seconds left in the window.
Called before the password hash runs: 600 000 iterations is a
denial-of-service lever, so a limited caller must not pay it — nor make
the server pay it. The returned guard holds the slot until it is
failed or dropped.
Sourcepub fn record_success(&self, client: Option<IpAddr>)
pub fn record_success(&self, client: Option<IpAddr>)
Clears an address’s counter after a completed login, so one operator fumbling their password does not spend the window for the next.
Trait Implementations§
Auto Trait Implementations§
impl !Freeze for LoginLimiter
impl RefUnwindSafe for LoginLimiter
impl Send for LoginLimiter
impl Sync for LoginLimiter
impl Unpin for LoginLimiter
impl UnsafeUnpin for LoginLimiter
impl UnwindSafe for LoginLimiter
Blanket Implementations§
Source§impl<'a, T, E> AsTaggedExplicit<'a, E> for Twhere
T: 'a,
impl<'a, T, E> AsTaggedExplicit<'a, E> for Twhere
T: 'a,
Source§impl<'a, T, E> AsTaggedImplicit<'a, E> for Twhere
T: 'a,
impl<'a, T, E> AsTaggedImplicit<'a, E> for Twhere
T: 'a,
Source§impl<T> BorrowMut<T> for Twhere
T: ?Sized,
impl<T> BorrowMut<T> for Twhere
T: ?Sized,
Source§fn borrow_mut(&mut self) -> &mut T
fn borrow_mut(&mut self) -> &mut T
impl<A, B, T> HttpServerConnExec<A, B> for Twhere
B: Body,
Source§impl<T> Instrument for T
impl<T> Instrument for T
Source§fn instrument(self, span: Span) -> Instrumented<Self> ⓘ
fn instrument(self, span: Span) -> Instrumented<Self> ⓘ
Source§fn in_current_span(self) -> Instrumented<Self> ⓘ
fn in_current_span(self) -> Instrumented<Self> ⓘ
Source§impl<T> IntoEither for T
impl<T> IntoEither for T
Source§fn into_either(self, into_left: bool) -> Either<Self, Self> ⓘ
fn into_either(self, into_left: bool) -> Either<Self, Self> ⓘ
self into a Left variant of Either<Self, Self>
if into_left is true.
Converts self into a Right variant of Either<Self, Self>
otherwise. Read moreSource§fn into_either_with<F>(self, into_left: F) -> Either<Self, Self> ⓘ
fn into_either_with<F>(self, into_left: F) -> Either<Self, Self> ⓘ
self into a Left variant of Either<Self, Self>
if into_left(&self) returns true.
Converts self into a Right variant of Either<Self, Self>
otherwise. Read more