67 lines
1.6 KiB
Python
67 lines
1.6 KiB
Python
from app.site_auth import LoginLimiter, TicketStore
|
|
|
|
|
|
class Clock:
|
|
def __init__(self):
|
|
self.t = 1000.0
|
|
|
|
def __call__(self):
|
|
return self.t
|
|
|
|
|
|
def test_limiter_blocks_after_limit():
|
|
lim = LoginLimiter(limit=3, window=60, now=Clock())
|
|
for _ in range(3):
|
|
assert not lim.blocked("ip")
|
|
lim.fail("ip")
|
|
assert lim.blocked("ip")
|
|
assert not lim.blocked("other-ip")
|
|
|
|
|
|
def test_limiter_forgets_expired_keys():
|
|
# made-up usernames make a new key per guess; once their strikes expire they're dropped
|
|
clock = Clock()
|
|
lim = LoginLimiter(limit=5, window=60, now=clock)
|
|
for i in range(100):
|
|
lim.fail(f"ip|user{i}")
|
|
clock.t += 61
|
|
lim.fail("ip|someone")
|
|
assert list(lim.fails) == ["ip|someone"]
|
|
|
|
|
|
def test_limiter_window_slides():
|
|
clock = Clock()
|
|
lim = LoginLimiter(limit=2, window=60, now=clock)
|
|
lim.fail("ip")
|
|
lim.fail("ip")
|
|
assert lim.blocked("ip")
|
|
clock.t += 61
|
|
assert not lim.blocked("ip")
|
|
|
|
|
|
def test_limiter_reset():
|
|
lim = LoginLimiter(limit=1, window=60, now=Clock())
|
|
lim.fail("ip")
|
|
lim.reset("ip")
|
|
assert not lim.blocked("ip")
|
|
|
|
|
|
def test_ticket_redeems_once():
|
|
store = TicketStore(ttl=60, now=Clock())
|
|
t = store.issue("tok")
|
|
assert len(t) >= 32
|
|
assert store.redeem(t) == "tok"
|
|
assert store.redeem(t) is None
|
|
|
|
|
|
def test_ticket_expires():
|
|
clock = Clock()
|
|
store = TicketStore(ttl=60, now=clock)
|
|
t = store.issue("tok")
|
|
clock.t += 61
|
|
assert store.redeem(t) is None
|
|
|
|
|
|
def test_unknown_ticket():
|
|
assert TicketStore().redeem("nope") is None
|