""" CSRF Protection for OAuth Authorization Implements token-based CSRF protection for the OAuth authorization flow. Tokens are stored in-memory with 10-minute expiry. Part of Phase E: Custom OAuth Authorization Page """ import secrets import time class CSRFTokenManager: """ Manages CSRF tokens for OAuth authorization requests. Features: - Generates cryptographically secure random tokens - In-memory storage with automatic expiry - 10-minute token lifetime - Automatic cleanup of expired tokens """ def __init__(self, token_lifetime_seconds: int = 600): """ Initialize CSRF token manager. Args: token_lifetime_seconds: Token lifetime in seconds (default: 600 = 10 minutes) """ self._tokens: dict[str, float] = {} # token -> expiry_timestamp self._token_lifetime = token_lifetime_seconds def generate_token(self) -> str: """ Generate a new CSRF token. Returns: Secure random token (32 bytes, hex-encoded = 64 characters) """ token = secrets.token_hex(32) expiry = time.time() + self._token_lifetime self._tokens[token] = expiry # Cleanup expired tokens (lazy cleanup) self._cleanup_expired() return token def validate_token(self, token: str, consume: bool = True) -> bool: """ Validate a CSRF token. Args: token: CSRF token to validate consume: If True, token is removed after validation (one-time use) Returns: True if token is valid and not expired, False otherwise """ if not token or token not in self._tokens: return False expiry = self._tokens[token] now = time.time() # Check if token is expired if now > expiry: # Remove expired token self._tokens.pop(token, None) return False # Token is valid if consume: # Remove token (one-time use) self._tokens.pop(token, None) return True def _cleanup_expired(self): """ Remove expired tokens from storage. Called automatically during token generation to prevent memory leaks. """ now = time.time() expired_tokens = [token for token, expiry in self._tokens.items() if now > expiry] for token in expired_tokens: self._tokens.pop(token, None) def get_stats(self) -> dict[str, int]: """ Get statistics about stored tokens. Returns: Dictionary with token statistics """ now = time.time() active_tokens = sum(1 for expiry in self._tokens.values() if expiry > now) expired_tokens = len(self._tokens) - active_tokens return { "total_tokens": len(self._tokens), "active_tokens": active_tokens, "expired_tokens": expired_tokens, "token_lifetime_seconds": self._token_lifetime, } # Global CSRF token manager instance _csrf_manager: CSRFTokenManager | None = None def get_csrf_manager() -> CSRFTokenManager: """ Get the global CSRF token manager instance. Returns: Global CSRFTokenManager instance (singleton) """ global _csrf_manager if _csrf_manager is None: _csrf_manager = CSRFTokenManager() return _csrf_manager