253 lines
9.1 KiB
Python
253 lines
9.1 KiB
Python
"""
|
|
Application configuration with security improvements and validation
|
|
"""
|
|
import os
|
|
import secrets
|
|
from datetime import datetime
|
|
from typing import List, Optional, Dict, Any
|
|
from pathlib import Path
|
|
|
|
from pydantic import BaseSettings, validator, Field
|
|
from pydantic.networks import AnyHttpUrl, PostgresDsn, RedisDsn
|
|
import structlog
|
|
|
|
logger = structlog.get_logger(__name__)
|
|
|
|
|
|
class Settings(BaseSettings):
|
|
"""Application settings with validation"""
|
|
|
|
# Application
|
|
PROJECT_NAME: str = "My Uploader Bot"
|
|
PROJECT_VERSION: str = "2.0.0"
|
|
PROJECT_HOST: AnyHttpUrl = Field(default="http://127.0.0.1:15100")
|
|
SANIC_PORT: int = Field(default=15100, ge=1000, le=65535)
|
|
DEBUG: bool = Field(default=False)
|
|
|
|
# Security
|
|
SECRET_KEY: str = Field(default_factory=lambda: secrets.token_urlsafe(32))
|
|
JWT_SECRET_KEY: str = Field(default_factory=lambda: secrets.token_urlsafe(32))
|
|
JWT_EXPIRE_MINUTES: int = Field(default=60 * 24 * 7) # 7 days
|
|
ENCRYPTION_KEY: Optional[str] = None
|
|
|
|
# Rate Limiting
|
|
RATE_LIMIT_REQUESTS: int = Field(default=100)
|
|
RATE_LIMIT_WINDOW: int = Field(default=60) # seconds
|
|
RATE_LIMIT_ENABLED: bool = Field(default=True)
|
|
|
|
# Database
|
|
DATABASE_URL: PostgresDsn = Field(
|
|
default="postgresql+asyncpg://user:password@localhost:5432/uploader_bot"
|
|
)
|
|
DATABASE_POOL_SIZE: int = Field(default=10, ge=1, le=100)
|
|
DATABASE_MAX_OVERFLOW: int = Field(default=20, ge=0, le=100)
|
|
DATABASE_ECHO: bool = Field(default=False)
|
|
|
|
# Redis
|
|
REDIS_URL: RedisDsn = Field(default="redis://localhost:6379/0")
|
|
REDIS_POOL_SIZE: int = Field(default=10, ge=1, le=100)
|
|
REDIS_TTL_DEFAULT: int = Field(default=3600) # 1 hour
|
|
REDIS_TTL_SHORT: int = Field(default=300) # 5 minutes
|
|
REDIS_TTL_LONG: int = Field(default=86400) # 24 hours
|
|
|
|
# File Storage
|
|
UPLOADS_DIR: Path = Field(default=Path("/app/data"))
|
|
MAX_FILE_SIZE: int = Field(default=100 * 1024 * 1024) # 100MB
|
|
ALLOWED_CONTENT_TYPES: List[str] = Field(default=[
|
|
'image/jpeg', 'image/png', 'image/gif', 'image/webp',
|
|
'video/mp4', 'video/webm', 'video/ogg', 'video/quicktime',
|
|
'audio/mpeg', 'audio/ogg', 'audio/wav', 'audio/mp4',
|
|
'text/plain', 'application/json'
|
|
])
|
|
|
|
# Telegram
|
|
TELEGRAM_API_KEY: str = Field(..., min_length=40)
|
|
CLIENT_TELEGRAM_API_KEY: str = Field(..., min_length=40)
|
|
TELEGRAM_WEBHOOK_ENABLED: bool = Field(default=False)
|
|
TELEGRAM_WEBHOOK_URL: Optional[AnyHttpUrl] = None
|
|
TELEGRAM_WEBHOOK_SECRET: str = Field(default_factory=lambda: secrets.token_urlsafe(32))
|
|
|
|
# TON Blockchain
|
|
TESTNET: bool = Field(default=False)
|
|
TONCENTER_HOST: AnyHttpUrl = Field(default="https://toncenter.com/api/v2/")
|
|
TONCENTER_API_KEY: Optional[str] = None
|
|
TONCENTER_V3_HOST: AnyHttpUrl = Field(default="https://toncenter.com/api/v3/")
|
|
MY_PLATFORM_CONTRACT: str = Field(default="EQDmWp6hbJlYUrXZKb9N88sOrTit630ZuRijfYdXEHLtheMY")
|
|
MY_FUND_ADDRESS: str = Field(default="UQDarChHFMOI2On9IdHJNeEKttqepgo0AY4bG1trw8OAAwMY")
|
|
|
|
# Logging
|
|
LOG_LEVEL: str = Field(default="INFO", regex="^(DEBUG|INFO|WARNING|ERROR|CRITICAL)$")
|
|
LOG_DIR: Path = Field(default=Path("logs"))
|
|
LOG_FORMAT: str = Field(default="json")
|
|
LOG_ROTATION: str = Field(default="1 day")
|
|
LOG_RETENTION: str = Field(default="30 days")
|
|
|
|
# Monitoring
|
|
METRICS_ENABLED: bool = Field(default=True)
|
|
METRICS_PORT: int = Field(default=9090, ge=1000, le=65535)
|
|
HEALTH_CHECK_ENABLED: bool = Field(default=True)
|
|
|
|
# Background Services
|
|
INDEXER_ENABLED: bool = Field(default=True)
|
|
INDEXER_INTERVAL: int = Field(default=5, ge=1, le=3600)
|
|
TON_DAEMON_ENABLED: bool = Field(default=True)
|
|
TON_DAEMON_INTERVAL: int = Field(default=3, ge=1, le=3600)
|
|
LICENSE_SERVICE_ENABLED: bool = Field(default=True)
|
|
LICENSE_SERVICE_INTERVAL: int = Field(default=10, ge=1, le=3600)
|
|
CONVERT_SERVICE_ENABLED: bool = Field(default=True)
|
|
CONVERT_SERVICE_INTERVAL: int = Field(default=30, ge=1, le=3600)
|
|
|
|
# Web App URLs
|
|
WEB_APP_URLS: Dict[str, str] = Field(default={
|
|
'uploadContent': "https://web2-client.vercel.app/uploadContent"
|
|
})
|
|
|
|
# Maintenance
|
|
MAINTENANCE_MODE: bool = Field(default=False)
|
|
MAINTENANCE_MESSAGE: str = Field(default="System is under maintenance")
|
|
|
|
# Development
|
|
MOCK_EXTERNAL_SERVICES: bool = Field(default=False)
|
|
DISABLE_WEBHOOKS: bool = Field(default=False)
|
|
|
|
@validator('UPLOADS_DIR')
|
|
def create_uploads_dir(cls, v):
|
|
"""Create uploads directory if it doesn't exist"""
|
|
if not v.exists():
|
|
v.mkdir(parents=True, exist_ok=True)
|
|
return v
|
|
|
|
@validator('LOG_DIR')
|
|
def create_log_dir(cls, v):
|
|
"""Create log directory if it doesn't exist"""
|
|
if not v.exists():
|
|
v.mkdir(parents=True, exist_ok=True)
|
|
return v
|
|
|
|
@validator('DATABASE_URL')
|
|
def validate_database_url(cls, v):
|
|
"""Validate database URL format"""
|
|
if not str(v).startswith('postgresql+asyncpg://'):
|
|
raise ValueError('Database URL must use asyncpg driver')
|
|
return v
|
|
|
|
@validator('TELEGRAM_API_KEY', 'CLIENT_TELEGRAM_API_KEY')
|
|
def validate_telegram_keys(cls, v):
|
|
"""Validate Telegram bot tokens format"""
|
|
parts = v.split(':')
|
|
if len(parts) != 2 or not parts[0].isdigit() or len(parts[1]) != 35:
|
|
raise ValueError('Invalid Telegram bot token format')
|
|
return v
|
|
|
|
@validator('SECRET_KEY', 'JWT_SECRET_KEY')
|
|
def validate_secret_keys(cls, v):
|
|
"""Validate secret keys length"""
|
|
if len(v) < 32:
|
|
raise ValueError('Secret keys must be at least 32 characters long')
|
|
return v
|
|
|
|
class Config:
|
|
env_file = ".env"
|
|
case_sensitive = True
|
|
validate_assignment = True
|
|
|
|
|
|
class SecurityConfig:
|
|
"""Security-related configurations"""
|
|
|
|
# CORS settings
|
|
CORS_ORIGINS = [
|
|
"https://web2-client.vercel.app",
|
|
"https://t.me",
|
|
"https://web.telegram.org"
|
|
]
|
|
|
|
# Content Security Policy
|
|
CSP_DIRECTIVES = {
|
|
'default-src': ["'self'"],
|
|
'script-src': ["'self'", "'unsafe-inline'", "https://cdn.jsdelivr.net"],
|
|
'style-src': ["'self'", "'unsafe-inline'", "https://cdn.jsdelivr.net"],
|
|
'img-src': ["'self'", "data:", "https:"],
|
|
'connect-src': ["'self'", "https://api.telegram.org"],
|
|
'frame-ancestors': ["'none'"],
|
|
'form-action': ["'self'"],
|
|
'base-uri': ["'self'"]
|
|
}
|
|
|
|
# Request size limits
|
|
MAX_REQUEST_SIZE = 100 * 1024 * 1024 # 100MB
|
|
MAX_JSON_SIZE = 10 * 1024 * 1024 # 10MB
|
|
|
|
# Session settings
|
|
SESSION_COOKIE_SECURE = True
|
|
SESSION_COOKIE_HTTPONLY = True
|
|
SESSION_COOKIE_SAMESITE = "Strict"
|
|
|
|
# Rate limiting patterns
|
|
RATE_LIMIT_PATTERNS = {
|
|
"auth": {"requests": 5, "window": 300}, # 5 requests per 5 minutes
|
|
"upload": {"requests": 10, "window": 3600}, # 10 uploads per hour
|
|
"api": {"requests": 100, "window": 60}, # 100 API calls per minute
|
|
"heavy": {"requests": 1, "window": 60} # 1 heavy operation per minute
|
|
}
|
|
|
|
|
|
# Create settings instance
|
|
settings = Settings()
|
|
|
|
# Expose commonly used settings
|
|
DATABASE_URL = str(settings.DATABASE_URL)
|
|
REDIS_URL = str(settings.REDIS_URL)
|
|
DATABASE_POOL_SIZE = settings.DATABASE_POOL_SIZE
|
|
DATABASE_MAX_OVERFLOW = settings.DATABASE_MAX_OVERFLOW
|
|
REDIS_POOL_SIZE = settings.REDIS_POOL_SIZE
|
|
|
|
TELEGRAM_API_KEY = settings.TELEGRAM_API_KEY
|
|
CLIENT_TELEGRAM_API_KEY = settings.CLIENT_TELEGRAM_API_KEY
|
|
PROJECT_HOST = str(settings.PROJECT_HOST)
|
|
SANIC_PORT = settings.SANIC_PORT
|
|
UPLOADS_DIR = settings.UPLOADS_DIR
|
|
ALLOWED_CONTENT_TYPES = settings.ALLOWED_CONTENT_TYPES
|
|
|
|
TESTNET = settings.TESTNET
|
|
TONCENTER_HOST = str(settings.TONCENTER_HOST)
|
|
TONCENTER_API_KEY = settings.TONCENTER_API_KEY
|
|
TONCENTER_V3_HOST = str(settings.TONCENTER_V3_HOST)
|
|
MY_PLATFORM_CONTRACT = settings.MY_PLATFORM_CONTRACT
|
|
MY_FUND_ADDRESS = settings.MY_FUND_ADDRESS
|
|
|
|
LOG_LEVEL = settings.LOG_LEVEL
|
|
LOG_DIR = settings.LOG_DIR
|
|
MAINTENANCE_MODE = settings.MAINTENANCE_MODE
|
|
|
|
# Cache keys patterns
|
|
CACHE_KEYS = {
|
|
"user_session": "user:session:{user_id}",
|
|
"user_data": "user:data:{user_id}",
|
|
"content_metadata": "content:meta:{content_id}",
|
|
"rate_limit": "rate_limit:{pattern}:{identifier}",
|
|
"blockchain_task": "blockchain:task:{task_id}",
|
|
"temp_upload": "upload:temp:{upload_id}",
|
|
"wallet_connection": "wallet:conn:{wallet_address}",
|
|
"ton_price": "ton:price:usd",
|
|
"system_status": "system:status:{service}",
|
|
}
|
|
|
|
# Log current configuration (without secrets)
|
|
def log_config():
|
|
"""Log current configuration without sensitive data"""
|
|
safe_config = {
|
|
"project_name": settings.PROJECT_NAME,
|
|
"project_version": settings.PROJECT_VERSION,
|
|
"debug": settings.DEBUG,
|
|
"sanic_port": settings.SANIC_PORT,
|
|
"testnet": settings.TESTNET,
|
|
"maintenance_mode": settings.MAINTENANCE_MODE,
|
|
"metrics_enabled": settings.METRICS_ENABLED,
|
|
"uploads_dir": str(settings.UPLOADS_DIR),
|
|
"log_level": settings.LOG_LEVEL,
|
|
}
|
|
logger.info("Configuration loaded", **safe_config)
|
|
|
|
# Initialize logging configuration
|
|
log_config() |