""" API Route Dependencies - Supabase Auth Integration """ from typing import Generator from fastapi import Depends, HTTPException, status from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials from sqlalchemy.orm import Session from app.core.database import SessionLocal from app.core.supabase_auth import supabase_auth from app.models.user import User import logging logger = logging.getLogger(__name__) security = HTTPBearer() def get_db() -> Generator[Session, None, None]: """ Database session dependency with automatic transaction management CRITICAL: Sessions MUST be committed or rolled back explicitly. If you don't commit, all changes are lost when the session closes. """ db = SessionLocal() try: yield db # AUTO-COMMIT: If endpoint doesn't raise exception, commit the transaction db.commit() except Exception: # AUTO-ROLLBACK: If endpoint raises exception, rollback the transaction db.rollback() raise finally: db.close() async def get_current_user( credentials: HTTPAuthorizationCredentials = Depends(security), db: Session = Depends(get_db) ) -> User: """ Get current authenticated user from Supabase Auth token Args: credentials: HTTP Bearer token (Supabase JWT) db: Database session Returns: Current user Raises: HTTPException: If token is invalid or user not found """ credentials_exception = HTTPException( status_code=status.HTTP_401_UNAUTHORIZED, detail="Could not validate credentials", headers={"WWW-Authenticate": "Bearer"}, ) try: token = credentials.credentials # Verify token with Supabase and get user auth_user = await supabase_auth.get_user(token) if not auth_user: logger.warning("Invalid or expired token") raise credentials_exception user_id = auth_user.id # Get user from our database user = db.query(User).filter( User.id == user_id, User.deleted_at == None ).first() if user is None: logger.warning(f"User not found in database: {user_id}") raise credentials_exception return user except HTTPException: raise except Exception as e: logger.error(f"Authentication error: {str(e)}") raise credentials_exception async def get_current_active_user( current_user: User = Depends(get_current_user) ) -> User: """ Get current active user Args: current_user: Current authenticated user Returns: Current active user Raises: HTTPException: If user is inactive """ logger.info(f"Checking active user: {current_user.id}, is_active: {current_user.is_active}, type: {type(current_user.is_active)}") # Handle None case explicitly if current_user.is_active is None: logger.warning(f"User {current_user.id} has NULL is_active field - defaulting to False") raise HTTPException( status_code=status.HTTP_403_FORBIDDEN, detail="User account status is undefined" ) if not current_user.is_active: logger.warning(f"User {current_user.id} is inactive") raise HTTPException( status_code=status.HTTP_403_FORBIDDEN, detail="Inactive user" ) logger.info(f"User {current_user.id} is active - proceeding") return current_user def get_org_scope(user: User) -> dict: """ Get organization scope for org admin users. Returns dict with org filtering info: - is_org_scoped: bool - Whether user is org admin (needs scoping) - client_id: UUID or None - Client org to filter by - contractor_id: UUID or None - Contractor org to filter by Platform admins and non-org-admins return is_org_scoped=False """ if user.role in ['client_admin', 'contractor_admin']: return { 'is_org_scoped': True, 'client_id': user.client_id, 'contractor_id': user.contractor_id, 'org_type': 'client' if user.client_id else 'contractor' } return { 'is_org_scoped': False, 'client_id': None, 'contractor_id': None, 'org_type': None }