""" Environment configuration for secure handling of sensitive data. This module provides secure environment variable handling and configuration management for the Aniworld server application. """ import os import secrets from typing import Optional, Dict, Any from dotenv import load_dotenv import logging logger = logging.getLogger(__name__) # Load environment variables from .env file load_dotenv() class EnvironmentConfig: """Manages environment variables and secure configuration.""" # Security SECRET_KEY: str = os.getenv('SECRET_KEY', secrets.token_urlsafe(32)) JWT_SECRET_KEY: str = os.getenv('JWT_SECRET_KEY', secrets.token_urlsafe(32)) PASSWORD_SALT: str = os.getenv('PASSWORD_SALT', secrets.token_hex(32)) # Database DATABASE_URL: str = os.getenv('DATABASE_URL', 'sqlite:///data/aniworld.db') DATABASE_PASSWORD: Optional[str] = os.getenv('DATABASE_PASSWORD') # Redis (for caching and sessions) REDIS_URL: str = os.getenv('REDIS_URL', 'redis://localhost:6379/0') REDIS_PASSWORD: Optional[str] = os.getenv('REDIS_PASSWORD') # API Keys and External Services ANIME_PROVIDER_API_KEY: Optional[str] = os.getenv('ANIME_PROVIDER_API_KEY') TMDB_API_KEY: Optional[str] = os.getenv('TMDB_API_KEY') # Email Configuration (for password reset) SMTP_SERVER: str = os.getenv('SMTP_SERVER', 'localhost') SMTP_PORT: int = int(os.getenv('SMTP_PORT', '587')) SMTP_USERNAME: Optional[str] = os.getenv('SMTP_USERNAME') SMTP_PASSWORD: Optional[str] = os.getenv('SMTP_PASSWORD') SMTP_USE_TLS: bool = os.getenv('SMTP_USE_TLS', 'true').lower() == 'true' FROM_EMAIL: str = os.getenv('FROM_EMAIL', 'noreply@aniworld.local') # Security Settings SESSION_TIMEOUT_HOURS: int = int(os.getenv('SESSION_TIMEOUT_HOURS', '24')) MAX_FAILED_LOGIN_ATTEMPTS: int = int(os.getenv('MAX_FAILED_LOGIN_ATTEMPTS', '5')) LOCKOUT_DURATION_MINUTES: int = int(os.getenv('LOCKOUT_DURATION_MINUTES', '30')) # Rate Limiting RATE_LIMIT_PER_MINUTE: int = int(os.getenv('RATE_LIMIT_PER_MINUTE', '60')) API_RATE_LIMIT_PER_MINUTE: int = int(os.getenv('API_RATE_LIMIT_PER_MINUTE', '100')) # Application Settings DEBUG: bool = os.getenv('DEBUG', 'false').lower() == 'true' HOST: str = os.getenv('HOST', '127.0.0.1') PORT: int = int(os.getenv('PORT', '5000')) # Anime Directory and Download Settings ANIME_DIRECTORY: str = os.getenv('ANIME_DIRECTORY', './downloads') MAX_CONCURRENT_DOWNLOADS: int = int(os.getenv('MAX_CONCURRENT_DOWNLOADS', '3')) DOWNLOAD_SPEED_LIMIT: Optional[int] = int(os.getenv('DOWNLOAD_SPEED_LIMIT', '0')) or None # Logging LOG_LEVEL: str = os.getenv('LOG_LEVEL', 'INFO') LOG_FILE: str = os.getenv('LOG_FILE', './logs/aniworld.log') @classmethod def get_database_config(cls) -> Dict[str, Any]: """Get database configuration.""" return { 'url': cls.DATABASE_URL, 'password': cls.DATABASE_PASSWORD, 'pool_size': int(os.getenv('DATABASE_POOL_SIZE', '10')), 'max_overflow': int(os.getenv('DATABASE_MAX_OVERFLOW', '20')), 'pool_timeout': int(os.getenv('DATABASE_POOL_TIMEOUT', '30')), 'pool_recycle': int(os.getenv('DATABASE_POOL_RECYCLE', '3600')) } @classmethod def get_redis_config(cls) -> Dict[str, Any]: """Get Redis configuration.""" return { 'url': cls.REDIS_URL, 'password': cls.REDIS_PASSWORD, 'max_connections': int(os.getenv('REDIS_MAX_CONNECTIONS', '10')), 'retry_on_timeout': True, 'socket_timeout': int(os.getenv('REDIS_SOCKET_TIMEOUT', '5')) } @classmethod def get_email_config(cls) -> Dict[str, Any]: """Get email configuration.""" return { 'server': cls.SMTP_SERVER, 'port': cls.SMTP_PORT, 'username': cls.SMTP_USERNAME, 'password': cls.SMTP_PASSWORD, 'use_tls': cls.SMTP_USE_TLS, 'from_email': cls.FROM_EMAIL } @classmethod def get_security_config(cls) -> Dict[str, Any]: """Get security configuration.""" return { 'secret_key': cls.SECRET_KEY, 'jwt_secret_key': cls.JWT_SECRET_KEY, 'password_salt': cls.PASSWORD_SALT, 'session_timeout_hours': cls.SESSION_TIMEOUT_HOURS, 'max_failed_attempts': cls.MAX_FAILED_LOGIN_ATTEMPTS, 'lockout_duration_minutes': cls.LOCKOUT_DURATION_MINUTES, 'rate_limit_per_minute': cls.RATE_LIMIT_PER_MINUTE, 'api_rate_limit_per_minute': cls.API_RATE_LIMIT_PER_MINUTE } @classmethod def validate_config(cls) -> bool: """Validate that required configuration is present.""" required_vars = [ 'SECRET_KEY', 'JWT_SECRET_KEY', 'PASSWORD_SALT' ] missing_vars = [] for var in required_vars: if not getattr(cls, var): missing_vars.append(var) if missing_vars: logger.error(f"Missing required environment variables: {missing_vars}") return False return True @classmethod def generate_env_template(cls, file_path: str = '.env.template') -> bool: """Generate a template .env file with all available configuration options.""" try: template_content = """# Aniworld Server Environment Configuration # Copy this file to .env and fill in your values # Security (REQUIRED - Generate secure random values) SECRET_KEY=your_secret_key_here JWT_SECRET_KEY=your_jwt_secret_here PASSWORD_SALT=your_password_salt_here # Database Configuration DATABASE_URL=sqlite:///data/aniworld.db # DATABASE_PASSWORD=your_db_password_here DATABASE_POOL_SIZE=10 DATABASE_MAX_OVERFLOW=20 DATABASE_POOL_TIMEOUT=30 DATABASE_POOL_RECYCLE=3600 # Redis Configuration (for caching and sessions) REDIS_URL=redis://localhost:6379/0 # REDIS_PASSWORD=your_redis_password_here REDIS_MAX_CONNECTIONS=10 REDIS_SOCKET_TIMEOUT=5 # Email Configuration (for password reset emails) SMTP_SERVER=localhost SMTP_PORT=587 # SMTP_USERNAME=your_smtp_username # SMTP_PASSWORD=your_smtp_password SMTP_USE_TLS=true FROM_EMAIL=noreply@aniworld.local # External API Keys # ANIME_PROVIDER_API_KEY=your_anime_provider_api_key # TMDB_API_KEY=your_tmdb_api_key # Security Settings SESSION_TIMEOUT_HOURS=24 MAX_FAILED_LOGIN_ATTEMPTS=5 LOCKOUT_DURATION_MINUTES=30 # Rate Limiting RATE_LIMIT_PER_MINUTE=60 API_RATE_LIMIT_PER_MINUTE=100 # Application Settings DEBUG=false HOST=127.0.0.1 PORT=5000 # Anime and Download Settings ANIME_DIRECTORY=./downloads MAX_CONCURRENT_DOWNLOADS=3 # DOWNLOAD_SPEED_LIMIT=1000000 # bytes per second # Logging LOG_LEVEL=INFO LOG_FILE=./logs/aniworld.log """ with open(file_path, 'w', encoding='utf-8') as f: f.write(template_content) logger.info(f"Environment template created at {file_path}") return True except Exception as e: logger.error(f"Error creating environment template: {e}") return False # Create global instance env_config = EnvironmentConfig() # Validate configuration on import if not env_config.validate_config(): logger.warning("Invalid environment configuration detected. Please check your .env file.")