"""Opaque browser credentials and CSRF protection.""" from __future__ import annotations import hashlib import hmac import secrets from typing import TYPE_CHECKING if TYPE_CHECKING: from pathlib import Path class CredentialService: """Hash browser credentials with a stable per-installation key.""" def __init__(self, key_path: Path) -> None: """Load or create the installation's credential-signing key.""" key_path.parent.mkdir(parents=True, exist_ok=True) if not key_path.exists(): key_path.write_bytes(secrets.token_bytes(32)) key_path.chmod(0o600) self._key = key_path.read_bytes() @staticmethod def issue() -> str: """Issue a cryptographically random browser credential.""" return secrets.token_urlsafe(32) def digest(self, credential: str) -> str: """Create the persistent keyed digest of a browser credential.""" return hmac.new(self._key, credential.encode(), hashlib.sha256).hexdigest() def csrf(self, credential: str, room_code: str) -> str: """Create a room-scoped CSRF token for a browser credential.""" return hmac.new(self._key, f"csrf:{credential}:{room_code}".encode(), hashlib.sha256).hexdigest() def valid_csrf(self, credential: str, room_code: str, candidate: str) -> bool: """Validate a candidate CSRF token using constant-time comparison.""" return hmac.compare_digest(self.csrf(credential, room_code), candidate)