from fastapi import Depends, HTTPException, status, Header from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials from typing import Optional from app.database import get_supabase from app.config import settings import logging logger = logging.getLogger(__name__) security = HTTPBearer(auto_error=False) async def get_current_user( credentials: Optional[HTTPAuthorizationCredentials] = Depends(security), ): """Extract and verify the current user from Supabase JWT""" if not credentials: raise HTTPException( status_code=status.HTTP_401_UNAUTHORIZED, detail="Not authenticated", ) token = credentials.credentials supabase = get_supabase() try: response = supabase.auth.get_user(token) if not response or not response.user: raise HTTPException( status_code=status.HTTP_401_UNAUTHORIZED, detail="Invalid or expired token", ) user = response.user # Check for suspension try: profile = supabase.table("user_profiles").select("suspended_at").eq("user_id", user.id).execute() if profile.data and profile.data[0].get("suspended_at"): raise HTTPException( status_code=status.HTTP_403_FORBIDDEN, detail="Account suspended. Please contact support.", ) except HTTPException: raise except Exception: pass # Don't block login if profile lookup fails return user except HTTPException: raise except Exception as e: logger.error(f"Auth error: {e}") raise HTTPException( status_code=status.HTTP_401_UNAUTHORIZED, detail="Invalid or expired token", ) async def get_admin_user( current_user=Depends(get_current_user), ): """Require the current user to be an admin.""" supabase = get_supabase() try: profile = supabase.table("user_profiles").select("is_admin").eq("user_id", current_user.id).execute() if not profile.data or not profile.data[0].get("is_admin"): raise HTTPException( status_code=status.HTTP_403_FORBIDDEN, detail="Admin access required", ) except HTTPException: raise except Exception as e: logger.error(f"Admin check failed: {e}") raise HTTPException( status_code=status.HTTP_403_FORBIDDEN, detail="Admin access required", ) return current_user async def get_optional_user( credentials: Optional[HTTPAuthorizationCredentials] = Depends(security), ): """Optional auth - returns None if not authenticated""" if not credentials: return None try: return await get_current_user(credentials) except HTTPException: return None async def get_user_subscription(user=Depends(get_current_user)): """Get user's subscription plan""" supabase = get_supabase() try: result = ( supabase.table("subscriptions") .select("*") .eq("user_id", user.id) .eq("status", "active") .execute() ) if result.data: return result.data[0] return {"plan": "free", "status": "active", "user_id": user.id} except Exception: return {"plan": "free", "status": "active", "user_id": user.id} async def require_plan(min_plan: str, user=Depends(get_current_user)): """Require a minimum plan level""" plan_order = ["free", "starter", "business", "agency", "enterprise"] subscription = await get_user_subscription(user) user_plan = subscription.get("plan", "free") if plan_order.index(user_plan) < plan_order.index(min_plan): raise HTTPException( status_code=status.HTTP_402_PAYMENT_REQUIRED, detail=f"This feature requires {min_plan} plan or higher", ) return user