"""Secure live-stream access. Camera credentials are never exposed. A customer can request short-lived, HMAC-signed access tokens for streams authorized for their own orders; the `verify` endpoint validates the token and returns the stream URL. """ import base64 import hashlib import hmac import json import time from django.conf import settings from apps.live_monitoring.models import Camera, WorkflowStepCamera from apps.orders.models import ProcessingOrder from apps.workflows.models import WorkflowStep class StreamAccessService: def can_access(self, user, camera: Camera) -> bool: if not user or not user.is_authenticated: return False if user.is_superuser: return True version_ids = ProcessingOrder.objects.filter(customer__user=user).values_list( "workflow_version_id", flat=True ) step_ids = WorkflowStep.objects.filter( version_id__in=version_ids, live_monitoring_enabled=True ).values_list("id", flat=True) return WorkflowStepCamera.objects.filter(camera=camera, step_id__in=step_ids).exists() def issue_token(self, camera: Camera, ttl: int = 3600) -> tuple: payload = {"camera": str(camera.id), "exp": int(time.time()) + ttl} raw = base64.urlsafe_b64encode(json.dumps(payload).encode()).decode().rstrip("=") sig = hmac.new(settings.SECRET_KEY.encode(), raw.encode(), hashlib.sha256).hexdigest() return f"{raw}.{sig}", payload["exp"] def verify_token(self, token: str): try: raw, sig = token.split(".", 1) except (ValueError, AttributeError): return None expected = hmac.new(settings.SECRET_KEY.encode(), raw.encode(), hashlib.sha256).hexdigest() if not hmac.compare_digest(expected, sig): return None try: pad = raw + "=" * (-len(raw) % 4) payload = json.loads(base64.urlsafe_b64decode(pad).decode()) except Exception: return None if payload.get("exp", 0) < int(time.time()): return None return payload