[split-required] [guardrail-change] Enforce 500 LOC budget across all services

Install LOC guardrails (check-loc.sh, architecture.md, pre-commit hook)
and split all 44 files exceeding 500 LOC into domain-focused modules:

- consent-service (Go): models, handlers, services, database splits
- backend-core (Python): security_api, rbac_api, pdf_service, auth splits
- admin-core (TypeScript): 5 page.tsx + sidebar extractions
- pitch-deck (TypeScript): 6 slides, 3 UI components, engine.ts splits
- voice-service (Python): enhanced_task_orchestrator split

Result: 0 violations, 36 exempted (pipeline, tests, pure-data files).
Go build verified clean. No behavior changes — pure structural splits.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Benjamin Admin
2026-04-27 00:09:30 +02:00
parent 5ef039a6bc
commit 92c86ec6ba
162 changed files with 23853 additions and 23034 deletions

View File

@@ -0,0 +1,164 @@
"""
FastAPI Authentication Dependencies and Factory Functions.
Provides:
- get_keycloak_config_from_env(): Create config from env vars
- get_authenticator(): Create HybridAuthenticator instance
- get_auth(): Global authenticator singleton
- get_current_user(): FastAPI dependency for authentication
- require_role(): FastAPI dependency factory for role-based access
"""
import os
import logging
from typing import Optional, Dict, Any
from fastapi import Request, HTTPException, Depends
from .keycloak_auth import (
KeycloakConfig,
KeycloakConfigError,
HybridAuthenticator,
TokenExpiredError,
TokenInvalidError,
)
logger = logging.getLogger(__name__)
# =============================================
# FACTORY FUNCTIONS
# =============================================
def get_keycloak_config_from_env() -> Optional[KeycloakConfig]:
"""
Create KeycloakConfig from environment variables.
Required env vars:
- KEYCLOAK_SERVER_URL: e.g., https://keycloak.breakpilot.app
- KEYCLOAK_REALM: e.g., breakpilot
- KEYCLOAK_CLIENT_ID: e.g., breakpilot-backend
Optional:
- KEYCLOAK_CLIENT_SECRET: For confidential clients
- KEYCLOAK_VERIFY_SSL: Default true
"""
server_url = os.environ.get("KEYCLOAK_SERVER_URL")
realm = os.environ.get("KEYCLOAK_REALM")
client_id = os.environ.get("KEYCLOAK_CLIENT_ID")
if not all([server_url, realm, client_id]):
logger.info("Keycloak not configured, using local JWT only")
return None
return KeycloakConfig(
server_url=server_url,
realm=realm,
client_id=client_id,
client_secret=os.environ.get("KEYCLOAK_CLIENT_SECRET"),
verify_ssl=os.environ.get("KEYCLOAK_VERIFY_SSL", "true").lower() == "true"
)
def get_authenticator() -> HybridAuthenticator:
"""
Get configured authenticator instance.
Uses environment variables to determine configuration.
"""
keycloak_config = get_keycloak_config_from_env()
# JWT_SECRET is required - no default fallback in production
jwt_secret = os.environ.get("JWT_SECRET")
environment = os.environ.get("ENVIRONMENT", "development")
if not jwt_secret and environment == "production":
raise KeycloakConfigError(
"JWT_SECRET environment variable is required in production"
)
return HybridAuthenticator(
keycloak_config=keycloak_config,
local_jwt_secret=jwt_secret,
environment=environment
)
# =============================================
# FASTAPI DEPENDENCY
# =============================================
# Global authenticator instance (lazy-initialized)
_authenticator: Optional[HybridAuthenticator] = None
def get_auth() -> HybridAuthenticator:
"""Get or create global authenticator."""
global _authenticator
if _authenticator is None:
_authenticator = get_authenticator()
return _authenticator
async def get_current_user(request: Request) -> Dict[str, Any]:
"""
FastAPI dependency to get current authenticated user.
Usage:
@app.get("/api/protected")
async def protected_endpoint(user: dict = Depends(get_current_user)):
return {"user_id": user["user_id"]}
"""
auth_header = request.headers.get("authorization", "")
if not auth_header.startswith("Bearer "):
# Check for development mode
environment = os.environ.get("ENVIRONMENT", "development")
if environment == "development":
# Return demo user in development without token
return {
"user_id": "10000000-0000-0000-0000-000000000024",
"email": "demo@breakpilot.app",
"role": "admin",
"realm_roles": ["admin"],
"tenant_id": "a0000000-0000-0000-0000-000000000001",
"auth_method": "development_bypass"
}
raise HTTPException(status_code=401, detail="Missing authorization header")
token = auth_header.split(" ")[1]
try:
auth = get_auth()
return await auth.validate_token(token)
except TokenExpiredError:
raise HTTPException(status_code=401, detail="Token expired")
except TokenInvalidError as e:
raise HTTPException(status_code=401, detail=str(e))
except Exception as e:
logger.error(f"Authentication failed: {e}")
raise HTTPException(status_code=401, detail="Authentication failed")
async def require_role(required_role: str):
"""
FastAPI dependency factory for role-based access.
Usage:
@app.get("/api/admin-only")
async def admin_endpoint(user: dict = Depends(require_role("admin"))):
return {"message": "Admin access granted"}
"""
async def role_checker(user: dict = Depends(get_current_user)) -> dict:
user_role = user.get("role", "user")
realm_roles = user.get("realm_roles", [])
if user_role == required_role or required_role in realm_roles:
return user
raise HTTPException(
status_code=403,
detail=f"Role '{required_role}' required"
)
return role_checker