mirror of
https://github.com/ruvnet/RuView
synced 2026-08-01 19:01:42 +00:00
chore(repo): move v1/ → archive/v1/ + add archive/README.md (#430)
The Rust port at v2/ has been the primary codebase since the rename in #427. The Python implementation at v1/ is no longer the active target; the only load-bearing path is the deterministic proof bundle at v1/data/proof/ (per ADR-011 / ADR-028 witness verification). Move the whole Python tree into archive/v1/ and document the policy in archive/README.md: no new features, bug fixes only when they affect a still-load-bearing path (currently just the proof), CI continues to verify the proof on every push and PR. Path references updated in 26 files via path-pattern sed (only matches v1/<known-child> patterns, never bare v1 or API URLs like /api/v1/). Two double-prefix typos (archive/archive/v1/) caught and hand-fixed in verify-pipeline.yml and ADR-011. Validated: - Python proof verify.py imports cleanly at archive/v1/data/proof/ (numpy/scipy still required; CI installs requirements-lock.txt from archive/v1/ now) - cargo test --workspace --no-default-features → 1,539 passed, 0 failed, 8 ignored (unaffected by Python tree relocation) - ESP32-S3 on COM7 untouched (no firmware paths changed) After-merge: contributors should re-run any local `python v1/...` commands as `python archive/v1/...` (CLAUDE.md and CHANGELOG already updated).
This commit is contained in:
@@ -0,0 +1,467 @@
|
||||
"""
|
||||
Dependency injection for WiFi-DensePose API
|
||||
"""
|
||||
|
||||
import logging
|
||||
from typing import Optional, Dict, Any
|
||||
from functools import lru_cache
|
||||
|
||||
from fastapi import Depends, HTTPException, status, Request
|
||||
from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials
|
||||
|
||||
from src.config.settings import get_settings
|
||||
from src.config.domains import get_domain_config
|
||||
from src.services.pose_service import PoseService
|
||||
from src.services.stream_service import StreamService
|
||||
from src.services.hardware_service import HardwareService
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
# Security scheme for JWT authentication
|
||||
security = HTTPBearer(auto_error=False)
|
||||
|
||||
|
||||
# Service dependencies
|
||||
@lru_cache()
|
||||
def get_pose_service() -> PoseService:
|
||||
"""Get pose service instance."""
|
||||
settings = get_settings()
|
||||
domain_config = get_domain_config()
|
||||
|
||||
return PoseService(
|
||||
settings=settings,
|
||||
domain_config=domain_config
|
||||
)
|
||||
|
||||
|
||||
@lru_cache()
|
||||
def get_stream_service() -> StreamService:
|
||||
"""Get stream service instance."""
|
||||
settings = get_settings()
|
||||
domain_config = get_domain_config()
|
||||
|
||||
return StreamService(
|
||||
settings=settings,
|
||||
domain_config=domain_config
|
||||
)
|
||||
|
||||
|
||||
@lru_cache()
|
||||
def get_hardware_service() -> HardwareService:
|
||||
"""Get hardware service instance."""
|
||||
settings = get_settings()
|
||||
domain_config = get_domain_config()
|
||||
|
||||
return HardwareService(
|
||||
settings=settings,
|
||||
domain_config=domain_config
|
||||
)
|
||||
|
||||
|
||||
# Authentication dependencies
|
||||
async def get_current_user(
|
||||
request: Request,
|
||||
credentials: Optional[HTTPAuthorizationCredentials] = Depends(security)
|
||||
) -> Optional[Dict[str, Any]]:
|
||||
"""Get current authenticated user."""
|
||||
settings = get_settings()
|
||||
|
||||
# Skip authentication if disabled
|
||||
if not settings.enable_authentication:
|
||||
return None
|
||||
|
||||
# Check if user is already set by middleware
|
||||
if hasattr(request.state, 'user') and request.state.user:
|
||||
return request.state.user
|
||||
|
||||
# No credentials provided
|
||||
if not credentials:
|
||||
return None
|
||||
|
||||
# Validate the JWT token
|
||||
# JWT validation must be configured via settings (e.g. JWT_SECRET, JWT_ALGORITHM)
|
||||
if settings.is_development:
|
||||
logger.warning(
|
||||
"Authentication credentials provided in development mode but JWT "
|
||||
"validation is not configured. Set up JWT authentication via "
|
||||
"environment variables (JWT_SECRET, JWT_ALGORITHM) or disable "
|
||||
"authentication. Rejecting request."
|
||||
)
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_401_UNAUTHORIZED,
|
||||
detail=(
|
||||
"JWT authentication is not configured. In development mode, either "
|
||||
"disable authentication (enable_authentication=False) or configure "
|
||||
"JWT validation. Returning mock users is not permitted in any environment."
|
||||
),
|
||||
headers={"WWW-Authenticate": "Bearer"},
|
||||
)
|
||||
|
||||
# In production, implement proper JWT validation
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_401_UNAUTHORIZED,
|
||||
detail=(
|
||||
"JWT authentication is not configured. Configure JWT_SECRET and "
|
||||
"JWT_ALGORITHM environment variables, or integrate an external "
|
||||
"identity provider. See docs/authentication.md for setup instructions."
|
||||
),
|
||||
headers={"WWW-Authenticate": "Bearer"},
|
||||
)
|
||||
|
||||
|
||||
async def get_current_active_user(
|
||||
current_user: Optional[Dict[str, Any]] = Depends(get_current_user)
|
||||
) -> Dict[str, Any]:
|
||||
"""Get current active user (required authentication)."""
|
||||
if not current_user:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_401_UNAUTHORIZED,
|
||||
detail="Authentication required",
|
||||
headers={"WWW-Authenticate": "Bearer"},
|
||||
)
|
||||
|
||||
# Check if user is active
|
||||
if not current_user.get("is_active", True):
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_403_FORBIDDEN,
|
||||
detail="Inactive user"
|
||||
)
|
||||
|
||||
return current_user
|
||||
|
||||
|
||||
async def get_admin_user(
|
||||
current_user: Dict[str, Any] = Depends(get_current_active_user)
|
||||
) -> Dict[str, Any]:
|
||||
"""Get current admin user (admin privileges required)."""
|
||||
if not current_user.get("is_admin", False):
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_403_FORBIDDEN,
|
||||
detail="Admin privileges required"
|
||||
)
|
||||
|
||||
return current_user
|
||||
|
||||
|
||||
# Permission dependencies
|
||||
def require_permission(permission: str):
|
||||
"""Dependency factory for permission checking."""
|
||||
|
||||
async def check_permission(
|
||||
current_user: Dict[str, Any] = Depends(get_current_active_user)
|
||||
) -> Dict[str, Any]:
|
||||
"""Check if user has required permission."""
|
||||
user_permissions = current_user.get("permissions", [])
|
||||
|
||||
# Admin users have all permissions
|
||||
if current_user.get("is_admin", False):
|
||||
return current_user
|
||||
|
||||
# Check specific permission
|
||||
if permission not in user_permissions:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_403_FORBIDDEN,
|
||||
detail=f"Permission '{permission}' required"
|
||||
)
|
||||
|
||||
return current_user
|
||||
|
||||
return check_permission
|
||||
|
||||
|
||||
# Zone access dependencies
|
||||
async def validate_zone_access(
|
||||
zone_id: str,
|
||||
current_user: Optional[Dict[str, Any]] = Depends(get_current_user)
|
||||
) -> str:
|
||||
"""Validate user access to a specific zone."""
|
||||
domain_config = get_domain_config()
|
||||
|
||||
# Check if zone exists
|
||||
zone = domain_config.get_zone(zone_id)
|
||||
if not zone:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_404_NOT_FOUND,
|
||||
detail=f"Zone '{zone_id}' not found"
|
||||
)
|
||||
|
||||
# Check if zone is enabled
|
||||
if not zone.enabled:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_403_FORBIDDEN,
|
||||
detail=f"Zone '{zone_id}' is disabled"
|
||||
)
|
||||
|
||||
# If authentication is enabled, check user access
|
||||
if current_user:
|
||||
# Admin users have access to all zones
|
||||
if current_user.get("is_admin", False):
|
||||
return zone_id
|
||||
|
||||
# Check user's zone permissions
|
||||
user_zones = current_user.get("zones", [])
|
||||
if user_zones and zone_id not in user_zones:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_403_FORBIDDEN,
|
||||
detail=f"Access denied to zone '{zone_id}'"
|
||||
)
|
||||
|
||||
return zone_id
|
||||
|
||||
|
||||
# Router access dependencies
|
||||
async def validate_router_access(
|
||||
router_id: str,
|
||||
current_user: Optional[Dict[str, Any]] = Depends(get_current_user)
|
||||
) -> str:
|
||||
"""Validate user access to a specific router."""
|
||||
domain_config = get_domain_config()
|
||||
|
||||
# Check if router exists
|
||||
router = domain_config.get_router(router_id)
|
||||
if not router:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_404_NOT_FOUND,
|
||||
detail=f"Router '{router_id}' not found"
|
||||
)
|
||||
|
||||
# Check if router is enabled
|
||||
if not router.enabled:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_403_FORBIDDEN,
|
||||
detail=f"Router '{router_id}' is disabled"
|
||||
)
|
||||
|
||||
# If authentication is enabled, check user access
|
||||
if current_user:
|
||||
# Admin users have access to all routers
|
||||
if current_user.get("is_admin", False):
|
||||
return router_id
|
||||
|
||||
# Check user's router permissions
|
||||
user_routers = current_user.get("routers", [])
|
||||
if user_routers and router_id not in user_routers:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_403_FORBIDDEN,
|
||||
detail=f"Access denied to router '{router_id}'"
|
||||
)
|
||||
|
||||
return router_id
|
||||
|
||||
|
||||
# Service health dependencies
|
||||
async def check_service_health(
|
||||
request: Request,
|
||||
service_name: str
|
||||
) -> bool:
|
||||
"""Check if a service is healthy."""
|
||||
try:
|
||||
if service_name == "pose":
|
||||
service = getattr(request.app.state, 'pose_service', None)
|
||||
elif service_name == "stream":
|
||||
service = getattr(request.app.state, 'stream_service', None)
|
||||
elif service_name == "hardware":
|
||||
service = getattr(request.app.state, 'hardware_service', None)
|
||||
else:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_400_BAD_REQUEST,
|
||||
detail=f"Unknown service: {service_name}"
|
||||
)
|
||||
|
||||
if not service:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_503_SERVICE_UNAVAILABLE,
|
||||
detail=f"Service '{service_name}' not available"
|
||||
)
|
||||
|
||||
# Check service health
|
||||
status_info = await service.get_status()
|
||||
if status_info.get("status") != "healthy":
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_503_SERVICE_UNAVAILABLE,
|
||||
detail=f"Service '{service_name}' is unhealthy: {status_info.get('error', 'Unknown error')}"
|
||||
)
|
||||
|
||||
return True
|
||||
|
||||
except HTTPException:
|
||||
raise
|
||||
except Exception as e:
|
||||
logger.error(f"Error checking service health for {service_name}: {e}")
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_503_SERVICE_UNAVAILABLE,
|
||||
detail=f"Service '{service_name}' health check failed"
|
||||
)
|
||||
|
||||
|
||||
# Rate limiting dependencies
|
||||
async def check_rate_limit(
|
||||
request: Request,
|
||||
current_user: Optional[Dict[str, Any]] = Depends(get_current_user)
|
||||
) -> bool:
|
||||
"""Check rate limiting status."""
|
||||
settings = get_settings()
|
||||
|
||||
# Skip if rate limiting is disabled
|
||||
if not settings.enable_rate_limiting:
|
||||
return True
|
||||
|
||||
# Rate limiting is handled by middleware
|
||||
# This dependency can be used for additional checks
|
||||
return True
|
||||
|
||||
|
||||
# Configuration dependencies
|
||||
def get_zone_config(zone_id: str = Depends(validate_zone_access)):
|
||||
"""Get zone configuration."""
|
||||
domain_config = get_domain_config()
|
||||
return domain_config.get_zone(zone_id)
|
||||
|
||||
|
||||
def get_router_config(router_id: str = Depends(validate_router_access)):
|
||||
"""Get router configuration."""
|
||||
domain_config = get_domain_config()
|
||||
return domain_config.get_router(router_id)
|
||||
|
||||
|
||||
# Pagination dependencies
|
||||
class PaginationParams:
|
||||
"""Pagination parameters."""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
page: int = 1,
|
||||
size: int = 20,
|
||||
max_size: int = 100
|
||||
):
|
||||
if page < 1:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_400_BAD_REQUEST,
|
||||
detail="Page must be >= 1"
|
||||
)
|
||||
|
||||
if size < 1:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_400_BAD_REQUEST,
|
||||
detail="Size must be >= 1"
|
||||
)
|
||||
|
||||
if size > max_size:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_400_BAD_REQUEST,
|
||||
detail=f"Size must be <= {max_size}"
|
||||
)
|
||||
|
||||
self.page = page
|
||||
self.size = size
|
||||
self.offset = (page - 1) * size
|
||||
self.limit = size
|
||||
|
||||
|
||||
def get_pagination_params(
|
||||
page: int = 1,
|
||||
size: int = 20
|
||||
) -> PaginationParams:
|
||||
"""Get pagination parameters."""
|
||||
return PaginationParams(page=page, size=size)
|
||||
|
||||
|
||||
# Query filter dependencies
|
||||
class QueryFilters:
|
||||
"""Common query filters."""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
start_time: Optional[str] = None,
|
||||
end_time: Optional[str] = None,
|
||||
min_confidence: Optional[float] = None,
|
||||
activity: Optional[str] = None
|
||||
):
|
||||
self.start_time = start_time
|
||||
self.end_time = end_time
|
||||
self.min_confidence = min_confidence
|
||||
self.activity = activity
|
||||
|
||||
# Validate confidence
|
||||
if min_confidence is not None:
|
||||
if not 0.0 <= min_confidence <= 1.0:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_400_BAD_REQUEST,
|
||||
detail="min_confidence must be between 0.0 and 1.0"
|
||||
)
|
||||
|
||||
|
||||
def get_query_filters(
|
||||
start_time: Optional[str] = None,
|
||||
end_time: Optional[str] = None,
|
||||
min_confidence: Optional[float] = None,
|
||||
activity: Optional[str] = None
|
||||
) -> QueryFilters:
|
||||
"""Get query filters."""
|
||||
return QueryFilters(
|
||||
start_time=start_time,
|
||||
end_time=end_time,
|
||||
min_confidence=min_confidence,
|
||||
activity=activity
|
||||
)
|
||||
|
||||
|
||||
# WebSocket dependencies
|
||||
async def get_websocket_user(
|
||||
websocket_token: Optional[str] = None
|
||||
) -> Optional[Dict[str, Any]]:
|
||||
"""Get user from WebSocket token."""
|
||||
settings = get_settings()
|
||||
|
||||
# Skip authentication if disabled
|
||||
if not settings.enable_authentication:
|
||||
return None
|
||||
|
||||
# Validate the WebSocket token
|
||||
if not websocket_token:
|
||||
return None
|
||||
|
||||
if settings.is_development:
|
||||
logger.warning(
|
||||
"WebSocket token provided in development mode but token validation "
|
||||
"is not configured. Rejecting. Disable authentication or configure "
|
||||
"JWT validation to allow WebSocket connections."
|
||||
)
|
||||
return None
|
||||
|
||||
# WebSocket token validation requires a configured JWT secret and issuer.
|
||||
# Until JWT settings are provided via environment variables
|
||||
# (JWT_SECRET_KEY, JWT_ALGORITHM), tokens are rejected to prevent
|
||||
# unauthorised access. Configure authentication settings and implement
|
||||
# token verification here using the same logic as get_current_user().
|
||||
logger.warning("WebSocket token validation requires JWT configuration. Rejecting token.")
|
||||
return None
|
||||
|
||||
|
||||
async def get_current_user_ws(
|
||||
websocket_token: Optional[str] = None
|
||||
) -> Optional[Dict[str, Any]]:
|
||||
"""Get current user for WebSocket connections."""
|
||||
return await get_websocket_user(websocket_token)
|
||||
|
||||
|
||||
# Authentication requirement dependencies
|
||||
async def require_auth(
|
||||
current_user: Dict[str, Any] = Depends(get_current_active_user)
|
||||
) -> Dict[str, Any]:
|
||||
"""Require authentication for endpoint access."""
|
||||
return current_user
|
||||
|
||||
|
||||
# Development dependencies
|
||||
async def development_only():
|
||||
"""Dependency that only allows access in development."""
|
||||
settings = get_settings()
|
||||
|
||||
if not settings.is_development:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_404_NOT_FOUND,
|
||||
detail="Endpoint not available in production"
|
||||
)
|
||||
|
||||
return True
|
||||
Reference in New Issue
Block a user