""" Security utilities for EVG Ultimate Team. Provides functions for password hashing, token generation, and authentication. """ from datetime import datetime, timedelta from typing import Optional from jose import JWTError, jwt from passlib.context import CryptContext from app.config import get_settings settings = get_settings() # ============================================================================= # Password Hashing # ============================================================================= # Password context for hashing and verification pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto") def hash_password(password: str) -> str: """ Hash a password using bcrypt. Args: password: Plain text password Returns: Hashed password string Example: >>> hashed = hash_password("my_password") >>> print(hashed) $2b$12$... """ return pwd_context.hash(password) def verify_password(plain_password: str, hashed_password: str) -> bool: """ Verify a password against a hashed password. Args: plain_password: Plain text password to verify hashed_password: Hashed password to compare against Returns: True if password matches, False otherwise Example: >>> hashed = hash_password("my_password") >>> verify_password("my_password", hashed) True >>> verify_password("wrong_password", hashed) False """ return pwd_context.verify(plain_password, hashed_password) # ============================================================================= # JWT Token Generation and Verification # ============================================================================= # Algorithm for JWT encoding/decoding ALGORITHM = "HS256" # Token expiration time (7 days for this event) ACCESS_TOKEN_EXPIRE_DAYS = 7 def create_access_token( data: dict, expires_delta: Optional[timedelta] = None ) -> str: """ Create a JWT access token. Args: data: Dictionary of data to encode in the token expires_delta: Optional custom expiration time Returns: Encoded JWT token string Example: >>> token = create_access_token({"sub": "user_5", "is_admin": False}) >>> print(token) eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9... """ to_encode = data.copy() # Set expiration time if expires_delta: expire = datetime.utcnow() + expires_delta else: expire = datetime.utcnow() + timedelta(days=ACCESS_TOKEN_EXPIRE_DAYS) to_encode.update({"exp": expire}) # Encode and return token encoded_jwt = jwt.encode( to_encode, settings.secret_key, algorithm=ALGORITHM ) return encoded_jwt def decode_access_token(token: str) -> Optional[dict]: """ Decode and verify a JWT access token. Args: token: JWT token string to decode Returns: Decoded token data as dictionary, or None if invalid Example: >>> token = create_access_token({"sub": "user_5"}) >>> payload = decode_access_token(token) >>> print(payload["sub"]) user_5 """ try: payload = jwt.decode( token, settings.secret_key, algorithms=[ALGORITHM] ) return payload except JWTError: return None def verify_token(token: str) -> dict: """ Verify a JWT access token and return payload. Args: token: JWT token string to verify Returns: Decoded token data as dictionary Raises: JWTError: If token is invalid or expired Example: >>> token = create_access_token({"sub": "user_5"}) >>> payload = verify_token(token) >>> print(payload["sub"]) user_5 """ payload = jwt.decode( token, settings.secret_key, algorithms=[ALGORITHM] ) return payload # ============================================================================= # Admin Authentication # ============================================================================= def verify_admin_credentials(username: str, password: str) -> bool: """ Verify admin credentials against environment configuration. Args: username: Admin username password: Admin password Returns: True if credentials are valid, False otherwise Example: >>> verify_admin_credentials("clement", "evg2026_admin") True >>> verify_admin_credentials("clement", "wrong_password") False """ return ( username.lower() == settings.admin_username.lower() and password == settings.admin_password ) # ============================================================================= # Token Payload Helpers # ============================================================================= def create_participant_token_data(participant_id: int, username: str, is_groom: bool = False) -> dict: """ Create token payload for a participant. Args: participant_id: Participant's ID username: Participant's username is_groom: Whether participant is the groom Returns: Dictionary with token payload data Example: >>> data = create_participant_token_data(5, "Hugo F.") >>> token = create_access_token(data) """ return { "sub": f"participant_{participant_id}", "user_id": participant_id, "username": username, "is_admin": False, "is_groom": is_groom, "type": "participant" } def create_admin_token_data(admin_id: int, username: str) -> dict: """ Create token payload for an admin. Args: admin_id: Admin's ID (can be 0 for the main admin) username: Admin's username Returns: Dictionary with token payload data Example: >>> data = create_admin_token_data(0, "clement") >>> token = create_access_token(data) """ return { "sub": f"admin_{admin_id}", "user_id": admin_id, "username": username, "is_admin": True, "is_groom": False, "type": "admin" } def extract_user_id_from_payload(payload: dict) -> Optional[int]: """ Extract user ID from token payload. Args: payload: Decoded JWT payload Returns: User ID if present, None otherwise Example: >>> payload = {"user_id": 5} >>> extract_user_id_from_payload(payload) 5 """ return payload.get("user_id") def is_admin_token(payload: dict) -> bool: """ Check if token payload represents an admin user. Args: payload: Decoded JWT payload Returns: True if admin, False otherwise Example: >>> payload = {"is_admin": True} >>> is_admin_token(payload) True """ return payload.get("is_admin", False)