Files
contexta_be/app/dependencies.py
belviskhoremk 92d4c2fc5e feat: add appointments, campaigns, admin, storage, tests and various updates
- Add new routers: admin, appointments, campaigns
- Add storage service and logging config
- Add migrations directory and test suite with pytest config
- Add supabase_migration_features.sql
- Update models, dependencies, config, and existing routers
- Remove whatsapp_service (deleted)
- Update pyproject.toml and uv.lock dependencies

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-03 09:11:58 +00:00

123 lines
3.9 KiB
Python

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