diff --git a/backend/.env.example b/backend/.env.example index f7247e992..cc0020144 100644 --- a/backend/.env.example +++ b/backend/.env.example @@ -1,6 +1,6 @@ # Supabase Configuration SUPABASE_URL=https://your-project.supabase.co -SUPABASE_SERVICE_KEY=your-service-key +SUPABASE_ANON_KEY=your-anon-key # Startup Mode # Set ALLOW_DEGRADED_STARTUP=1 to allow backend startup even if duplicate/RAG models fail to load diff --git a/backend/main.py b/backend/main.py index ae7da7c1f..3a7899d0d 100644 --- a/backend/main.py +++ b/backend/main.py @@ -35,13 +35,13 @@ env_path = Path(__file__).parent / '.env' load_dotenv(dotenv_path=env_path) -# Initialize Supabase Client (Service Role for backend bypass) +# Initialize Supabase Client (Anon Key to respect RLS) try: from supabase import create_client, Client url = os.environ.get("SUPABASE_URL") - key = os.environ.get("SUPABASE_SERVICE_KEY") + key = os.environ.get("SUPABASE_ANON_KEY") if not url or not key: - print("[ERROR] SUPABASE_URL or SUPABASE_SERVICE_KEY not set in backend/.env") + print("[ERROR] SUPABASE_URL or SUPABASE_ANON_KEY not set in backend/.env") supabase = None else: supabase = create_client(url, key) @@ -549,7 +549,7 @@ async def get_tickets(company_id: str | None = None): return res.data @app.post("/tickets/save") -async def save_ticket(request_body: TicketSaveRequest): +async def save_ticket(request_body: TicketSaveRequest, request: Request): """ OFFICIAL PERSISTENCE: Saves the analyzed ticket to Supabase. This is called AFTER the user confirms the analysis results. @@ -558,6 +558,30 @@ async def save_ticket(request_body: TicketSaveRequest): raise HTTPException(status_code=500, detail="Supabase connection not initialized.") logger = logging.getLogger(__name__) + + # --- 1000% Perfect Tenant Auth & Spoofing Protection --- + auth_header = request.headers.get("Authorization") + if not auth_header or not auth_header.startswith("Bearer "): + raise HTTPException(status_code=401, detail="Missing or invalid Authorization header") + + token = auth_header.split(" ")[1] + try: + user_response = supabase.auth.get_user(token) + if not user_response or not getattr(user_response, "user", None): + raise HTTPException(status_code=401, detail="Invalid token") + trusted_user_id = user_response.user.id + except Exception as e: + logger.error(f"Authentication failed: {e}") + raise HTTPException(status_code=401, detail="Authentication failed") + + # Enforce trusted user ID from token + if request_body.user_id and request_body.user_id != trusted_user_id: + logger.warning(f"ID Spoofing Attempt: Token user {trusted_user_id} tried to act as {request_body.user_id}") + raise HTTPException(status_code=403, detail="User ID mismatch. Spoofing detected.") + + request_body.user_id = trusted_user_id + # -------------------------------------------------------- + try: final_data = request_body.dict()