import hashlib import hmac import html import json import logging import os import secrets import threading import time from dataclasses import dataclass from pathlib import Path from typing import Any, Dict, List, Optional from uuid import uuid4 from quota_fallback import ( GRANT, REFUND, embed_parent_from, embed_parents, grant_request_nonce, is_eligible_embed, issue_ticket, request_header, request_host, safe_message, signing_secret, verify_grant, ) logger = logging.getLogger(__name__) FREE_LIMIT_REASON = "LABS_3DAI_FREE_LIMIT" GRANT_TTL_SECONDS = 180 SETTLED_GRANT_TTL_SECONDS = 3600 MAX_GRANT_ENTRIES = 512 MAX_GRANTS_PER_SESSION = 8 SIGNUP_URL = "https://app.skyebrowse.com/register" EMBED_LIMIT_MESSAGE = ( "You have used your free 3D AI generations. Upgrade to a Commercial plan to keep generating." ) EMBED_UNAVAILABLE_MESSAGE = "3D AI could not confirm your SkyeBrowse plan. Please try again." PUBLIC_LIMIT_MESSAGE = "You have used your {limit} free generations." PUBLIC_QUOTA_MESSAGE = "Free generations are busy right now." def env_flag(name: str) -> bool: return os.environ.get(name, "").strip().lower() in {"1", "true", "yes"} def env_int(name: str, default: int) -> int: try: return max(0, int(os.environ.get(name, "").strip() or default)) except ValueError: return default def grant_parents() -> Optional[frozenset]: raw = os.environ.get("LABS_EMBED_GRANTS", "").strip() if env_flag("LABS_EMBED_GRANTS"): return None return frozenset(item.strip() for item in raw.split(",") if item.strip()) def grants_required(request: Any) -> bool: if not is_eligible_embed(request): return False parents = grant_parents() if parents is None: return True return embed_parent_from(request_header(request, "referer"), request_host(request)) in parents def is_browser_visit(request: Any) -> bool: referer = request_header(request, "referer") return referer.startswith(("https://", "http://")) and not is_eligible_embed(request) def visitor_ip(request: Any) -> str: return request_header(request, "x-forwarded-for").split(",")[0].strip() @dataclass class GrantEntry: nonce: str session_hash: str issued_at: float settled_at: Optional[float] = None mode: Optional[str] = None notice: Optional[str] = None @dataclass class Access: mode: str = "native" nonce: Optional[str] = None visitor_key: Optional[str] = None notice: Optional[str] = None class GrantBook: def __init__( self, ttl: float = GRANT_TTL_SECONDS, max_entries: int = MAX_GRANT_ENTRIES, settled_ttl: float = SETTLED_GRANT_TTL_SECONDS, max_per_session: int = MAX_GRANTS_PER_SESSION, ): self._lock = threading.Lock() self._entries: Dict[str, GrantEntry] = {} self._lapsed: Dict[str, List[str]] = {} self._ttl = ttl self._settled_ttl = settled_ttl self._max_entries = max_entries self._max_per_session = max_per_session def request(self, session_hash: str) -> Optional[Dict[str, str]]: if not session_hash: return None with self._lock: self._purge() if len(self._entries) >= self._max_entries: return None if len(self._session_entries(session_hash)) >= self._max_per_session: return None entry = GrantEntry(uuid4().hex, session_hash, time.time()) self._entries[entry.nonce] = entry ticket = issue_ticket(entry.nonce, GRANT) return {"ticket": ticket} if ticket else None def settle(self, session_hash: str, ack: Any, receipt: Any) -> None: if not isinstance(ack, dict) or not isinstance(receipt, dict) or not receipt.get("ticket"): return if ack.get("ticket") != receipt.get("ticket"): return nonce = grant_request_nonce(receipt.get("ticket")) with self._lock: self._purge() entry = self._entries.get(nonce) if nonce else None if entry is None or entry.session_hash != session_hash or entry.settled_at is not None: return entry.settled_at = time.time() grant = verify_grant(ack.get("grant"), entry.nonce) if grant is not None: entry.mode = grant["mode"] elif ack.get("reason") == FREE_LIMIT_REASON: entry.notice = safe_message(ack.get("error"), EMBED_LIMIT_MESSAGE) else: entry.notice = safe_message(ack.get("error"), EMBED_UNAVAILABLE_MESSAGE) def take(self, session_hash: str) -> Access: with self._lock: self._purge() entry = self._pop_settled(session_hash) lapsed = None if entry else self._pop_lapsed(session_hash) if lapsed: return Access(mode="denied", nonce=lapsed, notice=embed_notice(EMBED_UNAVAILABLE_MESSAGE)) if entry is None or entry.mode is None: notice = entry.notice if entry and entry.notice else EMBED_UNAVAILABLE_MESSAGE return Access(mode="denied", notice=embed_notice(notice)) return Access(nonce=entry.nonce) def _pop_settled(self, session_hash: str) -> Optional[GrantEntry]: settled = [entry for entry in self._session_entries(session_hash) if entry.settled_at is not None] return self._entries.pop(min(settled, key=lambda entry: entry.settled_at).nonce) if settled else None def _pop_lapsed(self, session_hash: str) -> Optional[str]: lapsed = self._lapsed.get(session_hash) if not lapsed: return None nonce = lapsed.pop(0) if not lapsed: del self._lapsed[session_hash] return nonce def _session_entries(self, session_hash: str) -> List[GrantEntry]: return [entry for entry in self._entries.values() if entry.session_hash == session_hash] def _purge(self) -> None: now = time.time() for nonce, entry in list(self._entries.items()): pending = entry.settled_at is None started = entry.issued_at if pending else entry.settled_at if started < now - (self._ttl if pending else self._settled_ttl): del self._entries[nonce] if entry.mode is not None: self._lapse(entry) def _lapse(self, entry: GrantEntry) -> None: self._lapsed.setdefault(entry.session_hash, []).append(entry.nonce) while len(self._lapsed) > self._max_entries: del self._lapsed[next(iter(self._lapsed))] class PublicAllowance: def __init__(self, limit: int, window_seconds: float, path: Optional[Path]): self.limit = limit self._window = window_seconds self._path = path self._lock = threading.Lock() self._salt = (signing_secret() or secrets.token_hex(32)).encode("utf-8") self._uses: Dict[str, List[float]] = self._load() @property def enabled(self) -> bool: return self.limit > 0 def key_for(self, request: Any) -> Optional[str]: address = visitor_ip(request) if not address: logger.warning("Public allowance skipped: request has no X-Forwarded-For") return None return hmac.new(self._salt, address.encode("utf-8"), hashlib.sha256).hexdigest()[:32] def try_consume(self, key: Optional[str]) -> bool: if key is None: return True with self._lock: uses = self._recent(key) if len(uses) >= self.limit: return False self._uses[key] = uses + [time.time()] self._save() return True def refund(self, key: Optional[str]) -> None: if key is None: return with self._lock: uses = self._recent(key) if uses: self._uses[key] = uses[:-1] self._save() def used(self, key: Optional[str]) -> int: with self._lock: return len(self._recent(key)) if key else 0 def _recent(self, key: str) -> List[float]: cutoff = time.time() - self._window return [stamp for stamp in self._uses.get(key, []) if stamp > cutoff] def _load(self) -> Dict[str, List[float]]: if self._path is None or not self._path.exists(): return {} try: data = json.loads(self._path.read_text()) except (OSError, ValueError): return {} if not isinstance(data, dict): return {} return {str(key): [float(stamp) for stamp in value if isinstance(stamp, (int, float))] for key, value in data.items() if isinstance(value, list)} def _save(self) -> None: if self._path is None: return cutoff = time.time() - self._window snapshot = {key: [stamp for stamp in uses if stamp > cutoff] for key, uses in self._uses.items()} snapshot = {key: uses for key, uses in snapshot.items() if uses} self._uses = snapshot try: temporary = self._path.with_suffix(".tmp") temporary.write_text(json.dumps(snapshot)) temporary.replace(self._path) except OSError as error: logger.warning("Public allowance not persisted (%s)", type(error).__name__) def public_usage_path() -> Optional[Path]: configured = os.environ.get("LABS_PUBLIC_USAGE_PATH", "").strip() if configured: return Path(configured) base = Path("/data") if Path("/data").is_dir() else Path("/tmp") return base / "labs_3dai_public_usage.json" def public_allowance_from_env() -> PublicAllowance: hours = env_int("LABS_PUBLIC_WINDOW_HOURS", 720) or 720 return PublicAllowance(env_int("LABS_PUBLIC_FREE_LIMIT", 0), hours * 3600, public_usage_path()) def embed_notice(message: str) -> str: return f'