GT AI OS Community Edition v2.0.33
Security hardening release addressing CodeQL and Dependabot alerts: - Fix stack trace exposure in error responses - Add SSRF protection with DNS resolution checking - Implement proper URL hostname validation (replaces substring matching) - Add centralized path sanitization to prevent path traversal - Fix ReDoS vulnerability in email validation regex - Improve HTML sanitization in validation utilities - Fix capability wildcard matching in auth utilities - Update glob dependency to address CVE - Add CodeQL suppression comments for verified false positives 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
136
apps/control-panel-backend/app/core/database.py
Normal file
136
apps/control-panel-backend/app/core/database.py
Normal file
@@ -0,0 +1,136 @@
|
||||
"""
|
||||
Database configuration and utilities for GT 2.0 Control Panel
|
||||
"""
|
||||
import asyncio
|
||||
from contextlib import asynccontextmanager, contextmanager
|
||||
from sqlalchemy.ext.asyncio import create_async_engine, AsyncSession, async_sessionmaker
|
||||
from sqlalchemy import create_engine
|
||||
from sqlalchemy.orm import DeclarativeBase, sessionmaker, Session
|
||||
from sqlalchemy.pool import StaticPool
|
||||
import structlog
|
||||
|
||||
from app.core.config import settings
|
||||
|
||||
logger = structlog.get_logger()
|
||||
|
||||
# Create async engine
|
||||
engine = create_async_engine(
|
||||
settings.DATABASE_URL,
|
||||
echo=settings.DEBUG,
|
||||
future=True,
|
||||
pool_pre_ping=True,
|
||||
pool_size=10,
|
||||
max_overflow=20
|
||||
)
|
||||
|
||||
# Create sync engine for session management (Issue #264)
|
||||
# Uses psycopg2 instead of asyncpg for sync operations
|
||||
sync_database_url = settings.DATABASE_URL.replace("+asyncpg", "").replace("postgresql://", "postgresql+psycopg2://")
|
||||
if "+psycopg2" not in sync_database_url:
|
||||
sync_database_url = sync_database_url.replace("postgresql://", "postgresql+psycopg2://")
|
||||
|
||||
sync_engine = create_engine(
|
||||
sync_database_url,
|
||||
echo=settings.DEBUG,
|
||||
pool_pre_ping=True,
|
||||
pool_size=5,
|
||||
max_overflow=10
|
||||
)
|
||||
|
||||
# Create session makers
|
||||
async_session_maker = async_sessionmaker(
|
||||
engine,
|
||||
class_=AsyncSession,
|
||||
expire_on_commit=False
|
||||
)
|
||||
|
||||
sync_session_maker = sessionmaker(
|
||||
sync_engine,
|
||||
class_=Session,
|
||||
expire_on_commit=False
|
||||
)
|
||||
|
||||
|
||||
class Base(DeclarativeBase):
|
||||
"""Base class for all database models"""
|
||||
pass
|
||||
|
||||
|
||||
@asynccontextmanager
|
||||
async def get_db_session():
|
||||
"""Get database session context manager"""
|
||||
async with async_session_maker() as session:
|
||||
try:
|
||||
yield session
|
||||
await session.commit()
|
||||
except Exception:
|
||||
await session.rollback()
|
||||
raise
|
||||
finally:
|
||||
await session.close()
|
||||
|
||||
|
||||
async def get_db():
|
||||
"""Dependency for getting async database session"""
|
||||
async with get_db_session() as session:
|
||||
yield session
|
||||
|
||||
|
||||
@contextmanager
|
||||
def get_sync_db_session():
|
||||
"""Get synchronous database session context manager (for session management)"""
|
||||
session = sync_session_maker()
|
||||
try:
|
||||
yield session
|
||||
session.commit()
|
||||
except Exception:
|
||||
session.rollback()
|
||||
raise
|
||||
finally:
|
||||
session.close()
|
||||
|
||||
|
||||
def get_sync_db():
|
||||
"""Dependency for getting synchronous database session (for session management)"""
|
||||
with get_sync_db_session() as session:
|
||||
yield session
|
||||
|
||||
|
||||
async def init_db():
|
||||
"""Initialize database tables"""
|
||||
try:
|
||||
# Import all models to ensure they're registered
|
||||
from app.models import tenant, user, ai_resource, usage, audit, model_config, tenant_model_config
|
||||
|
||||
async with engine.begin() as conn:
|
||||
# Create all tables
|
||||
await conn.run_sync(Base.metadata.create_all)
|
||||
|
||||
logger.info("Database tables created successfully")
|
||||
|
||||
except Exception as e:
|
||||
logger.error("Failed to initialize database", error=str(e))
|
||||
raise
|
||||
|
||||
|
||||
async def check_db_connection():
|
||||
"""Check database connection health"""
|
||||
try:
|
||||
async with get_db_session() as session:
|
||||
await session.execute("SELECT 1")
|
||||
return True
|
||||
except Exception as e:
|
||||
logger.error("Database connection check failed", error=str(e))
|
||||
return False
|
||||
|
||||
|
||||
def create_database_url(
|
||||
username: str,
|
||||
password: str,
|
||||
host: str,
|
||||
port: int,
|
||||
database: str,
|
||||
driver: str = "postgresql+asyncpg"
|
||||
) -> str:
|
||||
"""Create database URL from components"""
|
||||
return f"{driver}://{username}:{password}@{host}:{port}/{database}"
|
||||
Reference in New Issue
Block a user