"""Session manager for single-user access control.""" import asyncio import time from typing import Optional from dataclasses import dataclass import uuid from .. import config @dataclass class Session: """Active session information.""" session_id: str client_ip: str user_agent: Optional[str] created_at: float last_activity: float class SessionManager: """Manages single active session for PM3 access.""" def __init__(self): """Initialize session manager.""" self._active_session: Optional[Session] = None self._lock = asyncio.Lock() def has_active_session(self) -> bool: """Check if there's an active session.""" if not self._active_session: return False # Check if session has timed out if time.time() - self._active_session.last_activity > config.SESSION_TIMEOUT: self._active_session = None return False return True async def create_session( self, client_ip: str, user_agent: Optional[str] = None, force_takeover: bool = False ) -> tuple[bool, Optional[str], Optional[str]]: """Create a new session. Args: client_ip: Client IP address user_agent: Client user agent string force_takeover: Force takeover of existing session Returns: Tuple of (success, session_id, error_message) """ async with self._lock: # Check if another session is active if self.has_active_session() and not force_takeover: return False, None, "Another session is active" # Create new session session_id = str(uuid.uuid4()) current_time = time.time() self._active_session = Session( session_id=session_id, client_ip=client_ip, user_agent=user_agent, created_at=current_time, last_activity=current_time ) return True, session_id, None async def release_session(self, session_id: str) -> bool: """Release a session. Args: session_id: Session ID to release Returns: True if session was released, False if not found """ async with self._lock: if self._active_session and self._active_session.session_id == session_id: self._active_session = None return True return False def update_activity(self, session_id: str) -> bool: """Update session activity timestamp. Args: session_id: Session ID to update Returns: True if updated, False if session not found """ if self._active_session and self._active_session.session_id == session_id: self._active_session.last_activity = time.time() return True return False def can_execute(self, session_id: Optional[str]) -> bool: """Check if a session can execute commands. Args: session_id: Session ID to check (None for no session) Returns: True if session can execute, False otherwise """ # No active session - allow execution if not self.has_active_session(): return True # Check if the provided session ID matches active session if session_id and self._active_session and self._active_session.session_id == session_id: return True return False def get_active_session(self) -> Optional[Session]: """Get the currently active session. Returns: Active session or None """ if self.has_active_session(): return self._active_session return None