2,059 bytes · the Python implementation · view raw
from typing import Any, List, Optional, Sequence
from .auth_login_throttle_types import ThrottleDecision, ThrottlePolicy
def _is_whole(value: Any) -> bool:
return isinstance(value, int) andnot isinstance(value, bool)
def login_throttle(failures: Sequence[int], now: int, policy: ThrottlePolicy) -> ThrottleDecision:
"""Allowed or locked, by replaying the failures in time order: max_attempts failures within window_seconds of the latest lock the account for lockout_seconds from that failure, and the count starts again."""ifnot _is_whole(policy.max_attempts) or policy.max_attempts < 1:
raise ValueError("maxAttempts must be a whole number of at least 1")
ifnot _is_whole(policy.window_seconds) or policy.window_seconds < 1:
raise ValueError("windowSeconds must be a whole number of at least 1")
ifnot _is_whole(policy.lockout_seconds) or policy.lockout_seconds < 1:
raise ValueError("lockoutSeconds must be a whole number of at least 1")
ifnot _is_whole(now):
raise TypeError("now must be a whole number of Unix seconds")
if isinstance(failures, (str, dict)) ornot all(_is_whole(t) for t in failures):
raise TypeError("failures must be whole Unix seconds")
locked_until: Optional[int] = None
streak: List[int] = []
for t in sorted(t for t in failures if t <= now):
if locked_until isnotNoneand t < locked_until:
continue
streak = [s for s in streak if s > t - policy.window_seconds]
streak.append(t)
if len(streak) >= policy.max_attempts:
locked_until = t + policy.lockout_seconds
streak = []
if locked_until isnotNoneand now < locked_until:
return ThrottleDecision(allowed=False, retry_after_seconds=locked_until - now, remaining_attempts=0, locked_until=locked_until)
live = sum(1for s in streak if s > now - policy.window_seconds)
return ThrottleDecision(allowed=True, retry_after_seconds=0, remaining_attempts=policy.max_attempts - live, locked_until=None)