""" Security utilities for the application. """ import secrets import string from typing import Optional import re def generate_secure_token(length: int = 32) -> str: """Generate a secure random token.""" alphabet = string.ascii_letters + string.digits return ''.join(secrets.choice(alphabet) for _ in range(length)) def validate_email(email: str) -> bool: """Validate email format.""" pattern = r'^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$' return re.match(pattern, email) is not None def validate_password_strength(password: str) -> tuple[bool, str]: """ Validate password strength. Returns (is_valid, message) """ if len(password) < 8: return False, "Password must be at least 8 characters long" if not re.search(r'[A-Z]', password): return False, "Password must contain at least one uppercase letter" if not re.search(r'[a-z]', password): return False, "Password must contain at least one lowercase letter" if not re.search(r'\d', password): return False, "Password must contain at least one digit" # Check for special characters if not re.search(r'[!@#$%^&*(),.?":{}|<>]', password): return False, "Password must contain at least one special character" return True, "Password is strong" def sanitize_filename(filename: str) -> str: """Sanitize filename for safe storage.""" # Remove path separators and dangerous characters sanitized = re.sub(r'[^\w\-_.]', '_', filename) # Limit length if len(sanitized) > 255: name, ext = sanitized.rsplit('.', 1) if '.' in sanitized else (sanitized, '') max_name_length = 255 - len(ext) - 1 if ext else 255 sanitized = name[:max_name_length] + ('.' + ext if ext else '') return sanitized def validate_uuid(uuid_string: str) -> bool: """Validate UUID format.""" uuid_pattern = r'^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$' return re.match(uuid_pattern, uuid_string, re.IGNORECASE) is not None class RateLimiter: """Simple in-memory rate limiter.""" def __init__(self): self.requests = {} def is_allowed(self, key: str, max_requests: int, window_seconds: int) -> bool: """Check if request is allowed under rate limit.""" import time current_time = time.time() if key not in self.requests: self.requests[key] = [] # Remove old requests outside the window self.requests[key] = [ req_time for req_time in self.requests[key] if current_time - req_time < window_seconds ] # Check if under limit if len(self.requests[key]) >= max_requests: return False # Add current request self.requests[key].append(current_time) return True # Global rate limiter instance rate_limiter = RateLimiter()