mirror of
https://github.com/suitenumerique/meet.git
synced 2026-08-13 12:17:24 +00:00
75 lines
2.3 KiB
Python
75 lines
2.3 KiB
Python
"""Service handling the lifecycle of transit codes.
|
|
|
|
A transit code is an opaque, cryptographically random, single-use code
|
|
handed to an embedded frontend (through a URL fragment) so it can obtain a
|
|
user access token on the core API without a session cookie. The code
|
|
carries no information by itself: everything it references (user, client)
|
|
is stored server-side in the cache, and consumed atomically on exchange.
|
|
"""
|
|
|
|
import hashlib
|
|
import secrets
|
|
|
|
from django.conf import settings
|
|
from django.core.cache import cache
|
|
|
|
|
|
class TransitCodeService:
|
|
"""Create and consume single-use transit codes."""
|
|
|
|
@staticmethod
|
|
def _cache_key(code):
|
|
"""Build the cache key for a code.
|
|
|
|
The code is hashed so that a dump of the cache never reveals
|
|
directly usable codes.
|
|
"""
|
|
digest = hashlib.sha256(code.encode("utf-8")).hexdigest()
|
|
return f"{settings.TRANSIT_CODE_CACHE_PREFIX}:{digest}"
|
|
|
|
def create_code(self, user, client_id="unknown"):
|
|
"""Generate a transit code for a user, and store it.
|
|
|
|
The code expires after TRANSIT_CODE_TTL seconds.
|
|
|
|
Returns:
|
|
str: The opaque code to hand to the client.
|
|
"""
|
|
# Default 48 random bytes -> 64 url-safe characters, 384 bits of
|
|
# entropy: unguessable and safe to transit through a URL fragment.
|
|
code = secrets.token_urlsafe(settings.TRANSIT_CODE_NBYTES)
|
|
|
|
cache.set(
|
|
self._cache_key(code),
|
|
{
|
|
"user_id": str(user.id),
|
|
"client_id": client_id,
|
|
},
|
|
timeout=settings.TRANSIT_CODE_TTL,
|
|
)
|
|
|
|
return code
|
|
|
|
def consume_code(self, code):
|
|
"""Consume a transit code, enforcing single use.
|
|
|
|
The code is deleted from the cache upon consumption. `cache.delete`
|
|
returns whether a key was actually deleted, so if two requests race
|
|
on the same code, only one of them wins.
|
|
|
|
Returns:
|
|
dict | None: The data stored at creation time ('user_id',
|
|
'client_id'), or None if the code is unknown, expired or
|
|
already consumed.
|
|
"""
|
|
if not code:
|
|
return None
|
|
|
|
key = self._cache_key(code)
|
|
data = cache.get(key)
|
|
|
|
if data is None or not cache.delete(key):
|
|
return None
|
|
|
|
return data
|