Spaces:
Running
Running
| """ | |
| Security utilities for API authentication and authorization | |
| """ | |
| from fastapi import HTTPException, status, Header | |
| from typing import Optional | |
| import logging | |
| from config import settings | |
| logger = logging.getLogger(__name__) | |
| def verify_api_key(x_api_key: Optional[str] = Header(None)) -> str: | |
| """ | |
| Verify API key from request header | |
| Args: | |
| x_api_key: API key from X-API-Key header | |
| Returns: | |
| API key if valid | |
| Raises: | |
| HTTPException: If API key is invalid or missing | |
| """ | |
| if not x_api_key: | |
| # API key is optional in development | |
| if settings.DEBUG: | |
| return "development" | |
| raise HTTPException( | |
| status_code=status.HTTP_401_UNAUTHORIZED, | |
| detail="API key required in X-API-Key header" | |
| ) | |
| if x_api_key != settings.API_KEY: | |
| logger.warning(f"Invalid API key attempt: {x_api_key[:10]}...") | |
| raise HTTPException( | |
| status_code=status.HTTP_401_UNAUTHORIZED, | |
| detail="Invalid API key" | |
| ) | |
| return x_api_key | |
| def is_admin_key(api_key: str) -> bool: | |
| """ | |
| Check if API key has admin privileges | |
| Args: | |
| api_key: API key to verify | |
| Returns: | |
| True if admin key, False otherwise | |
| """ | |
| return api_key == settings.API_KEY | |
| def hash_api_key(api_key: str) -> str: | |
| """ | |
| Hash API key for storage | |
| Args: | |
| api_key: Plain text API key | |
| Returns: | |
| Hashed API key | |
| """ | |
| import hashlib | |
| return hashlib.sha256(api_key.encode()).hexdigest() | |
| def verify_hashed_api_key(plain_key: str, hashed_key: str) -> bool: | |
| """ | |
| Verify plain key against hashed key | |
| Args: | |
| plain_key: Plain text API key | |
| hashed_key: Hashed API key | |
| Returns: | |
| True if keys match, False otherwise | |
| """ | |
| return hash_api_key(plain_key) == hashed_key | |
| class SecurityHeaders: | |
| """Security headers for responses""" | |
| HEADERS = { | |
| "X-Content-Type-Options": "nosniff", | |
| "X-Frame-Options": "DENY", | |
| "X-XSS-Protection": "1; mode=block", | |
| "Strict-Transport-Security": "max-age=31536000; includeSubDomains", | |
| "Content-Security-Policy": "default-src 'self'" | |
| } | |
| def get_headers() -> dict: | |
| """Get security headers""" | |
| return SecurityHeaders.HEADERS | |
| def rate_limit_check(client_ip: str, max_requests: int = 100, window_seconds: int = 60) -> bool: | |
| """ | |
| Check if client has exceeded rate limit | |
| (Simple in-memory implementation - use Redis for production) | |
| Args: | |
| client_ip: Client IP address | |
| max_requests: Max requests allowed | |
| window_seconds: Time window in seconds | |
| Returns: | |
| True if within limit, False if exceeded | |
| """ | |
| # In production, use Redis or similar | |
| # This is a placeholder implementation | |
| return True | |
| def validate_request_size(content_length: Optional[int], max_size: int) -> bool: | |
| """ | |
| Validate request content size | |
| Args: | |
| content_length: Content-Length header value | |
| max_size: Maximum allowed size in bytes | |
| Returns: | |
| True if valid, False if too large | |
| """ | |
| if content_length is None: | |
| return True | |
| return content_length <= max_size | |
| def sanitize_input(user_input: str, max_length: int = 5000) -> str: | |
| """ | |
| Sanitize user input | |
| Args: | |
| user_input: User provided input | |
| max_length: Maximum allowed length | |
| Returns: | |
| Sanitized input | |
| """ | |
| if not user_input: | |
| return "" | |
| # Truncate if too long | |
| user_input = user_input[:max_length] | |
| # Remove null bytes | |
| user_input = user_input.replace('\x00', '') | |
| return user_input.strip() | |
| def create_cors_headers() -> dict: | |
| """Create CORS headers""" | |
| return { | |
| "Access-Control-Allow-Origin": ", ".join(settings.CORS_ORIGINS), | |
| "Access-Control-Allow-Credentials": str(settings.CORS_CREDENTIALS).lower(), | |
| "Access-Control-Allow-Methods": ", ".join(settings.CORS_METHODS), | |
| "Access-Control-Allow-Headers": ", ".join(settings.CORS_HEADERS) | |
| } | |
| def log_security_event(event_type: str, details: dict): | |
| """ | |
| Log security-related events | |
| Args: | |
| event_type: Type of security event | |
| details: Event details | |
| """ | |
| logger.warning(f"SECURITY EVENT: {event_type} - {details}") | |
| def is_valid_submission_id(submission_id: str) -> bool: | |
| """ | |
| Validate submission ID format | |
| Args: | |
| submission_id: Submission ID to validate | |
| Returns: | |
| True if valid format, False otherwise | |
| """ | |
| if not submission_id or len(submission_id) == 0: | |
| return False | |
| # Check if it matches expected format (sub_xxxxxxxx) | |
| if submission_id.startswith("sub_") and len(submission_id) > 4: | |
| return True | |
| return False |