Some checks failed
Deploy Development / deploy (push) Successful in 49s
Test Suite / pytest-backend (push) Successful in 47s
Test Suite / lint-backend (push) Successful in 0s
Test Suite / build-frontend (push) Successful in 15s
Test Suite / k6 /health Baseline (push) Successful in 35s
Test Suite / playwright-tests (push) Failing after 5m43s
Limit brute-force attempts, require admin TOTP, and keep session tokens out of media URLs. Co-authored-by: Cursor <cursoragent@cursor.com>
50 lines
1.6 KiB
Python
50 lines
1.6 KiB
Python
"""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
|