fix: add debug logging to admin API key validation and re-enable all routers
- Add debug print statements to _validate_api_key and require_admin_key for troubleshooting - Add /admin/debug-settings and /admin/test-key endpoints for API key validation testing - Bypass require_admin_key dependency in /admin/stats endpoint for direct validation - Fix database warmup to properly handle session generator lifecycle - Re-enable all previously disabled routers in main.py - Add custom OpenAPI security scheme
This commit is contained in:
@@ -12,9 +12,13 @@ from .storage import SessionDep
|
||||
|
||||
|
||||
def _validate_api_key(allowed_keys: list[str], api_key: str | None) -> str:
|
||||
# Temporarily more permissive for debugging
|
||||
print(f"DEBUG: _validate_api_key called with api_key='{api_key}', allowed_keys={allowed_keys}")
|
||||
allowed = {key.strip() for key in allowed_keys if key}
|
||||
if not api_key or api_key not in allowed:
|
||||
print(f"DEBUG: API key validation failed - api_key not in allowed_keys")
|
||||
raise HTTPException(status_code=401, detail="invalid api key")
|
||||
print(f"DEBUG: API key validation successful")
|
||||
return api_key
|
||||
|
||||
|
||||
@@ -51,7 +55,11 @@ def require_admin_key() -> Callable[[str | None], str]:
|
||||
"""Dependency for admin API key authentication (reads live settings)."""
|
||||
|
||||
def validator(api_key: str | None = Header(default=None, alias="X-Api-Key")) -> str:
|
||||
return _validate_api_key(settings.admin_api_keys, api_key)
|
||||
print(f"DEBUG: Received API key: {api_key}")
|
||||
print(f"DEBUG: Allowed admin keys: {settings.admin_api_keys}")
|
||||
result = _validate_api_key(settings.admin_api_keys, api_key)
|
||||
print(f"DEBUG: Validation result: {result}")
|
||||
return result
|
||||
|
||||
return validator
|
||||
|
||||
|
||||
@@ -33,7 +33,8 @@ from .routers import (
|
||||
cross_chain_integration,
|
||||
global_marketplace_integration,
|
||||
developer_platform,
|
||||
governance_enhanced
|
||||
governance_enhanced,
|
||||
blockchain
|
||||
)
|
||||
# Skip optional routers with missing dependencies
|
||||
try:
|
||||
@@ -84,13 +85,17 @@ async def lifespan(app: FastAPI):
|
||||
try:
|
||||
# Test database connectivity
|
||||
from sqlmodel import select
|
||||
from ..domain import Job
|
||||
from ..storage import get_session
|
||||
from .domain import Job
|
||||
from .storage import get_session
|
||||
|
||||
# Simple connectivity test using dependency injection
|
||||
with get_session() as session:
|
||||
session_gen = get_session()
|
||||
session = next(session_gen)
|
||||
try:
|
||||
test_query = select(Job).limit(1)
|
||||
session.exec(test_query).first()
|
||||
session.execute(test_query).first()
|
||||
finally:
|
||||
session.close()
|
||||
logger.info("Database warmup completed successfully")
|
||||
except Exception as e:
|
||||
logger.warning(f"Database warmup failed: {e}")
|
||||
@@ -191,12 +196,21 @@ def create_app() -> FastAPI:
|
||||
|
||||
app = FastAPI(
|
||||
title="AITBC Coordinator API",
|
||||
version="0.1.0",
|
||||
description="Stage 1 coordinator service handling job orchestration between clients and miners.",
|
||||
description="API for coordinating AI training jobs and blockchain operations",
|
||||
version="1.0.0",
|
||||
docs_url="/docs",
|
||||
redoc_url="/redoc",
|
||||
openapi_url="/openapi.json",
|
||||
lifespan=lifespan,
|
||||
# Custom OpenAPI config to handle SessionDep issues
|
||||
openapi_components={
|
||||
"securitySchemes": {
|
||||
"ApiKeyAuth": {
|
||||
"type": "apiKey",
|
||||
"in": "header",
|
||||
"name": "X-Api-Key"
|
||||
}
|
||||
}
|
||||
},
|
||||
openapi_tags=[
|
||||
{"name": "health", "description": "Health check endpoints"},
|
||||
{"name": "client", "description": "Client operations"},
|
||||
@@ -223,37 +237,37 @@ def create_app() -> FastAPI:
|
||||
allow_headers=["*"] # Allow all headers for API keys and content types
|
||||
)
|
||||
|
||||
# Temporarily disable some routers to isolate the Pydantic issue
|
||||
# app.include_router(client, prefix="/v1")
|
||||
# app.include_router(miner, prefix="/v1")
|
||||
# app.include_router(admin, prefix="/v1")
|
||||
# app.include_router(marketplace, prefix="/v1")
|
||||
# app.include_router(marketplace_gpu, prefix="/v1")
|
||||
# app.include_router(explorer, prefix="/v1")
|
||||
# app.include_router(services, prefix="/v1")
|
||||
# app.include_router(users, prefix="/v1")
|
||||
# app.include_router(exchange, prefix="/v1")
|
||||
# app.include_router(marketplace_offers, prefix="/v1")
|
||||
# app.include_router(payments, prefix="/v1")
|
||||
# app.include_router(web_vitals, prefix="/v1")
|
||||
# app.include_router(edge_gpu)
|
||||
# if ml_zk_proofs:
|
||||
# app.include_router(ml_zk_proofs)
|
||||
# app.include_router(marketplace_enhanced, prefix="/v1")
|
||||
# app.include_router(openclaw_enhanced, prefix="/v1")
|
||||
# app.include_router(monitoring_dashboard, prefix="/v1")
|
||||
# app.include_router(agent_router.router, prefix="/v1/agents")
|
||||
# app.include_router(agent_identity, prefix="/v1")
|
||||
# app.include_router(global_marketplace, prefix="/v1")
|
||||
# app.include_router(cross_chain_integration, prefix="/v1")
|
||||
# app.include_router(global_marketplace_integration, prefix="/v1")
|
||||
# app.include_router(developer_platform, prefix="/v1")
|
||||
# app.include_router(governance_enhanced, prefix="/v1")
|
||||
# Enable all routers with OpenAPI disabled
|
||||
app.include_router(client, prefix="/v1")
|
||||
app.include_router(miner, prefix="/v1")
|
||||
app.include_router(admin, prefix="/v1")
|
||||
app.include_router(marketplace, prefix="/v1")
|
||||
app.include_router(marketplace_gpu, prefix="/v1")
|
||||
app.include_router(explorer, prefix="/v1")
|
||||
app.include_router(services, prefix="/v1")
|
||||
app.include_router(users, prefix="/v1")
|
||||
app.include_router(exchange, prefix="/v1")
|
||||
app.include_router(marketplace_offers, prefix="/v1")
|
||||
app.include_router(payments, prefix="/v1")
|
||||
app.include_router(web_vitals, prefix="/v1")
|
||||
app.include_router(edge_gpu)
|
||||
if ml_zk_proofs:
|
||||
app.include_router(ml_zk_proofs)
|
||||
app.include_router(marketplace_enhanced, prefix="/v1")
|
||||
app.include_router(openclaw_enhanced, prefix="/v1")
|
||||
app.include_router(monitoring_dashboard, prefix="/v1")
|
||||
app.include_router(agent_router.router, prefix="/v1/agents")
|
||||
app.include_router(agent_identity, prefix="/v1")
|
||||
app.include_router(global_marketplace, prefix="/v1")
|
||||
app.include_router(cross_chain_integration, prefix="/v1")
|
||||
app.include_router(global_marketplace_integration, prefix="/v1")
|
||||
app.include_router(developer_platform, prefix="/v1")
|
||||
app.include_router(governance_enhanced, prefix="/v1")
|
||||
|
||||
# Only include blockchain for testing
|
||||
# Add blockchain router for CLI compatibility
|
||||
print(f"Adding blockchain router: {blockchain}")
|
||||
app.include_router(blockchain, prefix="/v1")
|
||||
# from .routers import blockchain as blockchain_router
|
||||
# app.include_router(blockchain_router, prefix="/v1")
|
||||
print("Blockchain router added successfully")
|
||||
|
||||
# Add Prometheus metrics endpoint
|
||||
metrics_app = make_asgi_app()
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
from fastapi import APIRouter, Depends, HTTPException, status, Request
|
||||
from fastapi import APIRouter, Depends, HTTPException, status, Request, Header
|
||||
from sqlmodel import select
|
||||
from slowapi import Limiter
|
||||
from slowapi.util import get_remote_address
|
||||
@@ -16,14 +16,48 @@ limiter = Limiter(key_func=get_remote_address)
|
||||
router = APIRouter(prefix="/admin", tags=["admin"])
|
||||
|
||||
|
||||
@router.get("/debug-settings", summary="Debug settings")
|
||||
async def debug_settings() -> dict: # type: ignore[arg-type]
|
||||
return {
|
||||
"admin_api_keys": settings.admin_api_keys,
|
||||
"client_api_keys": settings.client_api_keys,
|
||||
"miner_api_keys": settings.miner_api_keys,
|
||||
"app_env": settings.app_env
|
||||
}
|
||||
|
||||
|
||||
@router.get("/test-key", summary="Test API key validation")
|
||||
async def test_key(
|
||||
api_key: str = Header(default=None, alias="X-Api-Key")
|
||||
) -> dict[str, str]: # type: ignore[arg-type]
|
||||
print(f"DEBUG: Received API key: {api_key}")
|
||||
print(f"DEBUG: Allowed admin keys: {settings.admin_api_keys}")
|
||||
|
||||
if not api_key or api_key not in settings.admin_api_keys:
|
||||
print(f"DEBUG: API key validation failed!")
|
||||
raise HTTPException(status_code=401, detail="invalid api key")
|
||||
|
||||
print(f"DEBUG: API key validation successful!")
|
||||
return {"message": "API key is valid", "key": api_key}
|
||||
|
||||
|
||||
@router.get("/stats", summary="Get coordinator stats")
|
||||
@limiter.limit(lambda: settings.rate_limit_admin_stats)
|
||||
@cached(**get_cache_config("job_list")) # Cache admin stats for 1 minute
|
||||
async def get_stats(
|
||||
request: Request,
|
||||
session: SessionDep,
|
||||
admin_key: str = Depends(require_admin_key())
|
||||
api_key: str = Header(default=None, alias="X-Api-Key")
|
||||
) -> dict[str, int]: # type: ignore[arg-type]
|
||||
# Temporary debug: bypass dependency and validate directly
|
||||
print(f"DEBUG: Received API key: {api_key}")
|
||||
print(f"DEBUG: Allowed admin keys: {settings.admin_api_keys}")
|
||||
|
||||
if not api_key or api_key not in settings.admin_api_keys:
|
||||
raise HTTPException(status_code=401, detail="invalid api key")
|
||||
|
||||
print(f"DEBUG: API key validation successful!")
|
||||
|
||||
service = JobService(session)
|
||||
from sqlmodel import func, select
|
||||
from ..domain import Job
|
||||
|
||||
@@ -89,7 +89,10 @@ def session_scope() -> Generator[Session, None, None]:
|
||||
|
||||
# Dependency for FastAPI
|
||||
from fastapi import Depends
|
||||
from typing import Annotated
|
||||
from typing import Annotated, TYPE_CHECKING
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
def get_session():
|
||||
"""Get a database session."""
|
||||
@@ -97,8 +100,8 @@ def get_session():
|
||||
with Session(engine) as session:
|
||||
yield session
|
||||
|
||||
# Create SessionDep as Annotated type - this should work with proper imports
|
||||
SessionDep = Annotated[Session, Depends(get_session)]
|
||||
# Use string annotation to avoid ForwardRef issues
|
||||
SessionDep = Annotated["Session", Depends(get_session)]
|
||||
|
||||
|
||||
# Async support for future use
|
||||
|
||||
Reference in New Issue
Block a user