from __future__ import annotations from dataclasses import dataclass from fastapi import Depends, Header, HTTPException, status from fastapi.security import HTTPAuthorizationCredentials from app.core.dependencies import bearer, get_current_user from app.core.security import decode_access_token from app.modules.sync import repository as sync_repo from app.modules.sync.schemas import HubAuthContext, TenantContext from app.modules.users.models import User @dataclass class HubPrincipal: farm_hub_id: str hub_site_id: str enterprise_id: str def get_hub_auth(authorization: str | None = Header(default=None)) -> HubPrincipal: if not authorization or not authorization.startswith("Hub "): raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="HUB_UNAUTHORIZED") token = authorization[4:].strip() if ":" not in token: raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="HUB_UNAUTHORIZED") hub_site_id, api_key = token.split(":", 1) hub = sync_repo.verify_hub_credential(hub_site_id.strip(), api_key.strip()) if not hub: raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="HUB_UNAUTHORIZED") return HubPrincipal(farm_hub_id=hub.id, hub_site_id=hub.hub_site_id, enterprise_id=hub.enterprise_id) def get_tenant_context( enterprise_id: str, user: User = Depends(get_current_user), credentials: HTTPAuthorizationCredentials | None = Depends(bearer), ) -> TenantContext: if user.is_superuser: return TenantContext( enterprise_id=enterprise_id, enterprise_role="admin", user_id=user.id, farm_hub_ids=None, ) if credentials: try: payload = decode_access_token(credentials.credentials) if payload.get("enterprise_id") == enterprise_id: farm_ids = payload.get("farm_ids") return TenantContext( enterprise_id=enterprise_id, enterprise_role=payload.get("enterprise_role", "viewer"), # type: ignore[arg-type] user_id=user.id, farm_hub_ids=farm_ids, ) except Exception: pass member = sync_repo.get_member(user.id, enterprise_id) if not member: raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail="ENTERPRISE_FORBIDDEN") farm_ids = None if member.role == "admin" else sync_repo.list_farm_access(user.id, enterprise_id) return TenantContext( enterprise_id=enterprise_id, enterprise_role=member.role, # type: ignore[arg-type] user_id=user.id, farm_hub_ids=farm_ids, ) def require_farm_access( farm_hub_id: str, enterprise_id: str, tenant: TenantContext, ) -> None: if tenant.enterprise_role == "admin" or tenant.farm_hub_ids is None: return if farm_hub_id not in (tenant.farm_hub_ids or []): raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail="FARM_ACCESS_FORBIDDEN") def require_enterprise_admin(tenant: TenantContext = Depends(get_tenant_context)) -> TenantContext: if tenant.enterprise_role != "admin": raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail="ENTERPRISE_ADMIN_ONLY") return tenant def require_enterprise_zootech(tenant: TenantContext = Depends(get_tenant_context)) -> TenantContext: if tenant.enterprise_role not in ("admin", "zootech"): raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail="ENTERPRISE_ZOOTECH_ONLY") return tenant