Source code for regstack.auth.clock
from __future__ import annotations
from datetime import UTC, datetime, timedelta
from typing import Protocol
[docs]
class Clock(Protocol):
"""Source of "now" — the seam that makes time-sensitive code testable.
JWT issuance, JWT expiry validation, lockout window calculations,
and bulk-revoke comparisons all read time through this protocol
rather than calling ``datetime.now()`` directly. Production passes
:class:`SystemClock`; tests pass :class:`FrozenClock` so a single
test can deterministically assert "this token expires in exactly
7200 seconds" without sleeping.
"""
[docs]
def now(self) -> datetime:
"""Return the current tz-aware UTC datetime."""
...
[docs]
class SystemClock:
"""Production :class:`Clock` — wraps ``datetime.now(UTC)``."""
[docs]
def now(self) -> datetime:
"""Return the current wall-clock time as a tz-aware UTC datetime."""
return datetime.now(UTC)
[docs]
class FrozenClock:
"""Test :class:`Clock` — returns a fixed timestamp until advanced.
Pin the clock to a known instant for the lifetime of a test, then
advance it explicitly to step over expiry boundaries::
clock = FrozenClock()
token, _ = codec.encode("user-1")
clock.advance(timedelta(seconds=7201)) # past exp
with pytest.raises(TokenError):
codec.decode(token)
"""
def __init__(self, start: datetime | None = None) -> None:
"""Pin the clock at ``start`` (default 2125-01-01 UTC).
The default is deliberately ~100 years in the *future*. MongoDB's
TTL monitor deletes documents by comparing their ``expires_at``
against real wall-clock time on a ~60s cycle — it knows nothing
about this injected clock. A frozen "now" in the past would give
every TTL-indexed test row (oauth_states, pending_registrations,
mfa_codes, login_attempts, blacklist) an already-elapsed
``expires_at``, and any test straddling a TTL sweep would have
its rows reaped mid-flight. A far-future pin keeps the reaper
permanently out of reach while staying deterministic.
Args:
start: The initial timestamp. Should be tz-aware. Defaults
to ``2125-01-01T00:00:00Z``.
"""
self._now = start or datetime(2125, 1, 1, tzinfo=UTC)
[docs]
def now(self) -> datetime:
"""Return the currently-pinned timestamp."""
return self._now
[docs]
def advance(self, delta: timedelta) -> None:
"""Move the clock forward by ``delta``.
Args:
delta: How far to advance. Negative values are accepted but
rarely useful.
"""
self._now += delta
[docs]
def set(self, when: datetime) -> None:
"""Reset the clock to an absolute instant.
Args:
when: The new "now". Should be tz-aware.
"""
self._now = when