"""Stable, expiring signatures for media URLs. The SPA loads media with ````/```` tags, which cannot send the API's ``Authorization`` header, so those URLs carry a signature instead. The signature has to be *stable*: a URL that changes on every response makes the browser treat every refetch as a new resource and re-download the file. URLs are signed with a plain ``Signer`` (no per-second timestamp) plus an explicit ``exp`` claim quantized to a bucket, so every request inside a bucket mints the exact same URL. The URL rotates once per bucket and is valid for at least ``URL_TTL_SECONDS`` and at most ``URL_TTL_SECONDS + URL_BUCKET_SECONDS``. """ import time from django.core import signing URL_TTL_SECONDS = 7 * 86400 URL_BUCKET_SECONDS = 24 * 3600 _BUCKETS = URL_TTL_SECONDS // URL_BUCKET_SECONDS def _expiry(now=None): current = time.time() if now is None else now bucket = int(current // URL_BUCKET_SECONDS) return (bucket + _BUCKETS + 1) * URL_BUCKET_SECONDS def sign_payload(payload, salt, now=None): """Sign a payload with a stable, bucket-quantized expiry.""" return signing.Signer(salt=salt).sign_object( {**payload, "exp": _expiry(now)} ) def load_payload(signature, salt, legacy_max_age=86400): """Verify a signed payload; ``None`` when missing, tampered with or expired. Signatures minted before the stable scheme (``TimestampSigner``) are still accepted for one release so pages open across the deploy keep working. """ try: data = signing.Signer(salt=salt).unsign_object(signature) except signing.BadSignature: try: return signing.TimestampSigner(salt=salt).unsign_object( signature, max_age=legacy_max_age ) except signing.BadSignature: return None if not isinstance(data, dict): return None try: expired = int(data.get("exp", 0)) < time.time() except (TypeError, ValueError): return None return None if expired else data