diff --git a/backend/app/core/config.py b/backend/app/core/config.py index 5af58d2..8370082 100644 --- a/backend/app/core/config.py +++ b/backend/app/core/config.py @@ -1,6 +1,6 @@ -# Configuration settings for FastAPI app from pydantic_settings import BaseSettings -from typing import Optional +from typing import Optional, Dict, Any +import json class Settings(BaseSettings): # Supabase Configuration @@ -26,11 +26,15 @@ class Settings(BaseSettings): # Application Settings app_name: Optional[str] = None - # JWT Authentication - SUPABASE_JWT_SECRET: str # JWT Secret from Supabase Dashboard → Settings → API → JWT Settings + # JWT Authentication (RAW JSON STRING) + SUPABASE_JWT_PUBLIC_KEY: str model_config = { "env_file": ".env" } + @property + def supabase_jwt_jwk(self) -> Dict[str, Any]: + return json.loads(self.SUPABASE_JWT_PUBLIC_KEY) + settings = Settings() diff --git a/backend/app/core/dependencies.py b/backend/app/core/dependencies.py index aeb8c03..e0ec7d6 100644 --- a/backend/app/core/dependencies.py +++ b/backend/app/core/dependencies.py @@ -215,4 +215,3 @@ async def route(user = Depends(get_optional_user)): return await get_current_user(credentials) except HTTPException: return None - diff --git a/backend/app/core/security.py b/backend/app/core/security.py index 23dd3a7..93f3d85 100644 --- a/backend/app/core/security.py +++ b/backend/app/core/security.py @@ -3,153 +3,76 @@ Handles token validation, creation, and user verification """ -import jwt -from datetime import datetime, timedelta -from typing import Optional, Dict, Any +from jose import jwt, JWTError from fastapi import HTTPException, status +from typing import Optional, Dict, Any from app.core.config import settings class JWTHandler: - """Handle all JWT operations with Supabase tokens""" + """Handle Supabase JWT verification only""" def __init__(self): - try: - self.secret_key = settings.SUPABASE_JWT_SECRET - if not self.secret_key: - raise ValueError("SUPABASE_JWT_SECRET is not set in environment variables") - except Exception as e: - raise ValueError(f"Failed to load JWT secret: {str(e)}. Please set SUPABASE_JWT_SECRET in your .env file.") - self.algorithm = "HS256" # Supabase uses HS256 for legacy keys + self.public_key = settings.SUPABASE_JWT_PUBLIC_KEY + if not self.public_key: + raise ValueError("SUPABASE_JWT_PUBLIC_KEY is not set") - def decode_token(self, token: str) -> Dict[str, Any]: - """ - Decode and validate JWT token from Supabase + self.algorithm = "ES256" - Args: - token: JWT token string (without 'Bearer ' prefix) - - Returns: - Decoded payload with user information - - Raises: - HTTPException: If token is invalid or expired - """ + def decode_token(self, token: str) -> Dict[str, Any]: try: - # Decode JWT using Supabase secret - # Note: We don't verify audience as Supabase tokens may have different audience claims - # The signature verification is sufficient for security payload = jwt.decode( token, - self.secret_key, + self.public_key, # ✅ PUBLIC KEY algorithms=[self.algorithm], - options={ - "verify_signature": True, - "verify_exp": True, - "verify_iat": True, - "verify_aud": False # Disable audience verification for Supabase tokens - } + audience="authenticated", # Supabase default ) - # Validate required fields - if 'sub' not in payload: + if "sub" not in payload: raise HTTPException( status_code=status.HTTP_401_UNAUTHORIZED, - detail="Invalid token: missing user ID" + detail="Invalid token: missing user id", ) return payload - except jwt.ExpiredSignatureError: + except JWTError as e: raise HTTPException( status_code=status.HTTP_401_UNAUTHORIZED, - detail="Token has expired" - ) - - except jwt.InvalidTokenError as e: - raise HTTPException( - status_code=status.HTTP_401_UNAUTHORIZED, - detail=f"Invalid token: {str(e)}" + detail=f"Invalid token: {str(e)}", ) def verify_token(self, token: str) -> bool: - """ - Quick token validation without full decode - - Args: - token: JWT token string - - Returns: - True if valid, False otherwise - """ try: jwt.decode( token, - self.secret_key, + self.public_key, algorithms=[self.algorithm], - options={ - "verify_signature": True, - "verify_exp": True, - "verify_aud": False # Disable audience verification - } + audience="authenticated", ) return True - except: + except JWTError: return False def get_user_id_from_token(self, token: str) -> str: - """ - Extract user ID from token - - Args: - token: JWT token string - - Returns: - User UUID as string - """ - payload = self.decode_token(token) - return payload['sub'] + return self.decode_token(token)["sub"] def get_user_email_from_token(self, token: str) -> Optional[str]: - """ - Extract user email from token - - Args: - token: JWT token string - - Returns: - User email if present in token - """ - payload = self.decode_token(token) - return payload.get('email') + return self.decode_token(token).get("email") def get_user_role_from_token(self, token: str) -> Optional[str]: - """ - Extract user role from token metadata - - Args: - token: JWT token string - - Returns: - User role (creator/brand) if present - """ payload = self.decode_token(token) - - # Supabase stores custom claims in user_metadata or app_metadata - user_metadata = payload.get('user_metadata', {}) - app_metadata = payload.get('app_metadata', {}) - return ( - user_metadata.get('role') or - app_metadata.get('role') or - payload.get('role') + payload.get("user_metadata", {}).get("role") + or payload.get("app_metadata", {}).get("role") + or payload.get("role") ) -# Singleton instance jwt_handler = JWTHandler() + # Legacy functions for backward compatibility (optional - requires passlib) def verify_password(plain_password: str, hashed_password: str) -> bool: """Verify a password against a hash (if needed for custom auth)""" @@ -169,4 +92,3 @@ def get_password_hash(password: str) -> str: return pwd_context.hash(password) except ImportError: raise ImportError("passlib is required for password hashing. Install with: pip install passlib[bcrypt]") - diff --git a/backend/app/main.py b/backend/app/main.py index 6a6f434..f05d402 100644 --- a/backend/app/main.py +++ b/backend/app/main.py @@ -1,43 +1,36 @@ from fastapi import FastAPI from fastapi.middleware.cors import CORSMiddleware -import os -from app.api.routes import health -from app.core.supabase_clients import supabase_anon as supabase -from app.api.routes import auth -from app.api.routes import gemini_generate -from app.api.routes import campaigns -from app.api.routes import groq_generate -from app.api.routes import collaborations -from app.api.routes import creators -from app.api.routes import proposals -from app.api.routes import analytics -from app.api.routes import ai_analytics -from app.api.routes import profiles -app = FastAPI(title="Inpact Backend", version="0.1.0") -# Verify Supabase client initialization on startup -try: - # Try a lightweight query - response = supabase.table("_supabase_test").select("*").limit(1).execute() - print("✅ Supabase client initialized successfully.") -except Exception as e: - error_msg = str(e) - if "Could not find the table" in error_msg: - print("⚠️ Supabase client connected, but test table does not exist. Connection is working.") - else: - print(f"❌ Failed to verify Supabase connection: {e}") +from app.api.routes import ( + health, + auth, + gemini_generate, + campaigns, + groq_generate, + collaborations, + creators, + proposals, + analytics, + ai_analytics, + profiles, +) -# --- CORS Setup --- +app = FastAPI(title="Inpact Backend", version="0.1.0") + +# 🔥 FORCE OPEN CORS (DEBUG MODE) app.add_middleware( CORSMiddleware, - allow_origins=os.getenv("ALLOWED_ORIGINS", "http://localhost:3000").split(","), - allow_credentials=True, + allow_origins=["*"], + allow_credentials=False, # must be False when using "*" allow_methods=["*"], allow_headers=["*"], ) -app.include_router(gemini_generate.router) + + +# Routes app.include_router(health.router) app.include_router(auth.router) +app.include_router(gemini_generate.router) app.include_router(campaigns.router) app.include_router(groq_generate.router) app.include_router(collaborations.router) @@ -49,4 +42,5 @@ @app.get("/") def root(): - return {"message": "Welcome to Inpact Backend 🚀"} + return {"message": "Inpact Backend Running 🚀"} + diff --git a/backend/env_example b/backend/env_example index 4e08603..1d6ad33 100644 --- a/backend/env_example +++ b/backend/env_example @@ -33,4 +33,4 @@ ALLOWED_ORIGINS=http://localhost:3000,http://localhost:3001 # Location: Dashboard → Project Settings → API → JWT Settings → JWT Secret # Use the JWT Secret (NOT the anon key!) -SUPABASE_JWT_SECRET=your-jwt-secret-from-supabase-dashboard +SUPABASE_JWT_SECRET=your_jwt_key diff --git a/backend/test_jwt.py b/backend/test_jwt.py index ded0b39..d2d34e6 100644 --- a/backend/test_jwt.py +++ b/backend/test_jwt.py @@ -6,6 +6,7 @@ import requests import os from dotenv import load_dotenv +from jose import jwt, JWTError load_dotenv() @@ -15,8 +16,10 @@ SUPABASE_ANON_KEY = os.getenv("SUPABASE_KEY") # Test user credentials (update with your test account) -TEST_EMAIL = "test@example.com" -TEST_PASSWORD = "Test123!@#" +TEST_EMAIL = "anu906162@gmail.com" +TEST_PASSWORD = "@rani00@" +# @rani00@ + def get_jwt_token(): @@ -107,3 +110,4 @@ def test_invalid_token(): print("=== Tests Complete ===") + diff --git a/frontend/app/creator/campaign-wall/page.tsx b/frontend/app/creator/campaign-wall/page.tsx index 091cf17..7dded57 100644 --- a/frontend/app/creator/campaign-wall/page.tsx +++ b/frontend/app/creator/campaign-wall/page.tsx @@ -277,14 +277,14 @@ export default function CampaignWallPage() {