"""Kurzlebige Medien-Tokens. Kein Sitzungstoken — nur für Dateiabruf per Query.""" from __future__ import annotations import hashlib import hmac import os import time from typing import Optional MEDIA_GRANT_TTL_SECONDS = 600 _PREFIX = "mg1" def _key() -> bytes: dedicated = (os.getenv("MEDIA_TOKEN_SECRET") or "").strip() if dedicated: return hashlib.sha256(dedicated.encode("utf-8")).digest() material = (os.getenv("DB_PASSWORD") or "shinkan-dev-media").encode("utf-8") return hashlib.sha256(b"shinkan-media-grant-v1|" + material).digest() def issue_media_grant(profile_id: int, *, ttl_seconds: int = MEDIA_GRANT_TTL_SECONDS) -> tuple[str, int]: exp = int(time.time()) + int(ttl_seconds) payload = f"{int(profile_id)}.{exp}" sig = hmac.new(_key(), payload.encode("ascii"), hashlib.sha256).hexdigest() return f"{_PREFIX}.{payload}.{sig}", exp def profile_id_from_media_grant(token: Optional[str], *, now: Optional[int] = None) -> Optional[int]: if not token or not token.startswith(f"{_PREFIX}."): return None parts = token.split(".") if len(parts) != 4: return None _, pid_s, exp_s, sig = parts if not pid_s.isdigit() or not exp_s.isdigit(): return None payload = f"{pid_s}.{exp_s}" expected = hmac.new(_key(), payload.encode("ascii"), hashlib.sha256).hexdigest() if not hmac.compare_digest(expected, sig): return None exp = int(exp_s) moment = int(time.time() if now is None else now) if exp < moment: return None pid = int(pid_s) if pid < 1: return None return pid