from __future__ import annotations import base64 import hmac import hashlib import os import time from dataclasses import dataclass from typing import Optional COOKIE_NAME = "voley_admin" def _get_secret() -> bytes: secret = os.getenv("SECRET_KEY", "").strip() if not secret: # Разрешаем запуск без секрета, но с сильно сниженной безопасностью. # Для реального деплоя SECRET_KEY обязателен. secret = "dev-unsafe-secret" return secret.encode("utf-8") def _b64url(b: bytes) -> str: return base64.urlsafe_b64encode(b).decode("ascii").rstrip("=") def _b64url_decode(s: str) -> bytes: pad = "=" * (-len(s) % 4) return base64.urlsafe_b64decode((s + pad).encode("ascii")) def _sign(payload: bytes) -> str: mac = hmac.new(_get_secret(), payload, hashlib.sha256).digest() return _b64url(mac) def make_session(ttl_seconds: int = 60 * 60 * 12) -> str: exp = int(time.time()) + int(ttl_seconds) payload = f"exp={exp}".encode("utf-8") sig = _sign(payload) return f"{_b64url(payload)}.{sig}" def verify_session(token: str) -> bool: try: payload_b64, sig = token.split(".", 1) payload = _b64url_decode(payload_b64) except Exception: return False if not hmac.compare_digest(_sign(payload), sig): return False try: parts = payload.decode("utf-8").split("=", 1) if len(parts) != 2 or parts[0] != "exp": return False exp = int(parts[1]) except Exception: return False return time.time() < exp def check_admin_password(password: str) -> bool: expected = os.getenv("ADMIN_PASSWORD", "") if not expected: # Чтобы админка не осталась открытой по умолчанию. return False return hmac.compare_digest(password or "", expected) @dataclass(frozen=True) class AdminAuth: is_admin: bool token: Optional[str] = None