import os import re import traceback from fastapi import FastAPI, HTTPException, Depends, Request, status from pydantic import BaseModel from dotenv import load_dotenv from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials import jwt from jwt import PyJWKClient import base64 from slowapi import Limiter, _rate_limit_exceeded_handler from slowapi.util import get_remote_address from slowapi.errors import RateLimitExceeded # Load environment variables from .env file load_dotenv() if not os.getenv("GROQ_API_KEY") and not os.getenv("HF_TOKEN"): load_dotenv(dotenv_path='.env.example') if not os.getenv("GROQ_API_KEY") and os.getenv("HF_TOKEN"): os.environ["GROQ_API_KEY"] = os.getenv("HF_TOKEN") from humanizer import humanize_text from usage import check_usage_limit, increment_usage, get_user_profile, upgrade_user_plan, cancel_user_subscription from fastapi.middleware.cors import CORSMiddleware from fastapi import Request import hmac import json import razorpay RAZORPAY_KEY_ID = os.getenv("RAZORPAY_KEY_ID") RAZORPAY_KEY_SECRET = os.getenv("RAZORPAY_KEY_SECRET") RAZORPAY_WEBHOOK_SECRET = os.getenv("RAZORPAY_WEBHOOK_SECRET") RAZORPAY_STARTER_PLAN_ID = os.getenv("RAZORPAY_STARTER_PLAN_ID") RAZORPAY_PRO_PLAN_ID = os.getenv("RAZORPAY_PRO_PLAN_ID") razorpay_client = razorpay.Client(auth=(RAZORPAY_KEY_ID or "", RAZORPAY_KEY_SECRET or "")) SUPABASE_URL = os.getenv("SUPABASE_URL") JWKS_URL = f"{SUPABASE_URL}/auth/v1/.well-known/jwks.json" if SUPABASE_URL else "" jwks_client = PyJWKClient(JWKS_URL) if JWKS_URL else None security = HTTPBearer() def verify_token(credentials: HTTPAuthorizationCredentials = Depends(security)): token = credentials.credentials try: if not jwks_client: raise ValueError("SUPABASE_URL is not set.") signing_key = jwks_client.get_signing_key_from_jwt(token) payload = jwt.decode( token, signing_key.key, algorithms=["HS256", "RS256", "ES256"], audience="authenticated", options={"verify_aud": True} ) user_id = payload.get("sub") if not user_id: raise HTTPException(status_code=401, detail="Invalid token claims") return user_id except Exception as e: raise HTTPException(status_code=401, detail=f"Invalid or expired token: {str(e)}") INJECTION_PATTERNS = [ r"ignore\s*(all\s*)?(previous|prior|above)\s*(instructions?|prompts?|context)", r"forget\s*(what|everything|all)\s*(you\s*)?(were\s*)?(told|said|given|know)", r"you\s*are\s*now\s*(a|an|the)", r"act\s*as\s*(if\s*)?(you\s*are\s*)?(a|an|the)", r"new\s*(system\s*)?prompt", r"override\s*(system|instructions?|prompt)", r"pretend\s*(you\s*are|to\s*be)", r"\b(DAN|STAN)\b", r"(jailbreak|developer\s*mode|god\s*mode)", r"disregard\s*(all\s*)?(previous|prior)\s*(instructions?|rules?)", r"your\s*(true|real|actual)\s*(self|purpose|goal|task)", ] def sanitize_input(text: str) -> str: for pattern in INJECTION_PATTERNS: if re.search(pattern, text, re.IGNORECASE): raise HTTPException(status_code=400, detail="Input contains disallowed content.") try: decoded = base64.b64decode(text + "==").decode("utf-8", errors="ignore") for pattern in INJECTION_PATTERNS: if re.search(pattern, decoded, re.IGNORECASE): raise HTTPException(status_code=400, detail="Input contains disallowed content.") except Exception: pass return text app = FastAPI(title="AI Humanizer API") limiter = Limiter(key_func=get_remote_address) app.state.limiter = limiter app.add_exception_handler(RateLimitExceeded, _rate_limit_exceeded_handler) app.add_middleware( CORSMiddleware, allow_origins=[ "https://humanizer-frontend-beige.vercel.app", "http://localhost:3000" ], allow_methods=["*"], allow_headers=["*"], ) class HumanizeRequest(BaseModel): text: str mode: str = "standard" readability: str = "neutral" purpose: str = "professional" class HumanizeResponse(BaseModel): humanized: str mode: str readability: str purpose: str usage: dict # { count, limit, plan } class CreateOrderRequest(BaseModel): plan: str class VerifyPaymentRequest(BaseModel): razorpay_order_id: str razorpay_payment_id: str razorpay_signature: str user_id: str plan: str @app.post("/humanize", response_model=HumanizeResponse) @limiter.limit("10/minute") async def humanize(request: Request, body: HumanizeRequest, user_id: str = Depends(verify_token)): # Force enhanced mode as requested body.mode = "enhanced" # Check usage limit usage = check_usage_limit(user_id) if not usage["allowed"]: raise HTTPException( status_code=402, detail={ "message": "You have reached your free limit. Please upgrade to continue.", "plan": usage["plan"], "count": usage["count"], "limit": usage["limit"], } ) clean_text = sanitize_input(body.text) if not clean_text: raise HTTPException( status_code=400, detail="Input text is empty or invalid after sanitization." ) try: humanized_text = await humanize_text( clean_text, mode=body.mode, readability=body.readability, purpose=body.purpose, ) # Increment usage after successful humanization increment_usage(user_id) return HumanizeResponse( humanized=humanized_text, mode=body.mode, readability=body.readability, purpose=body.purpose, usage={ "count": usage["count"] + 1, "limit": usage["limit"], "plan": usage["plan"], } ) except Exception as e: import traceback raise HTTPException(status_code=500, detail=traceback.format_exc()) @app.get("/user-plan") async def get_user_plan(user_id: str = Depends(verify_token)): profile = get_user_profile(user_id) if not profile: raise HTTPException(status_code=404, detail="User not found") return {"plan": profile.get("plan", "free")} @app.post("/api/create-order") @limiter.limit("5/minute") async def create_order(request: Request, body: CreateOrderRequest, user_id: str = Depends(verify_token)): if not user_id: raise HTTPException(status_code=401, detail="Unauthorized") if body.plan not in ["starter", "pro"]: raise HTTPException(status_code=400, detail="Invalid plan") # Map plans to amounts (e.g. Starter = $9 = ₹750 = 75000 paise) amount = 75000 if request.plan == "starter" else 150000 if amount < 100: raise HTTPException(status_code=400, detail="Amount must be at least 100 paise") try: order_data = { "amount": amount, "currency": "INR", "receipt": request.user_id } order = razorpay_client.order.create(order_data) return { "order_id": order["id"], "amount": amount, "currency": "INR", "key_id": RAZORPAY_KEY_ID } except Exception as e: raise HTTPException(status_code=500, detail=str(e)) @app.post("/api/verify-payment") async def verify_payment(request: VerifyPaymentRequest): if not request.razorpay_order_id or not request.razorpay_payment_id or not request.razorpay_signature: raise HTTPException(status_code=400, detail="Missing required fields") try: expected_signature = hmac.new( key=RAZORPAY_KEY_SECRET.encode('utf-8'), msg=(request.razorpay_order_id + "|" + request.razorpay_payment_id).encode('utf-8'), digestmod='sha256' ).hexdigest() if not hmac.compare_digest(expected_signature, request.razorpay_signature): raise HTTPException(status_code=400, detail="Invalid signature") profile = get_user_profile(request.user_id) if profile and profile.get("plan") != request.plan: upgrade_user_plan(request.user_id, request.plan) return {"status": "ok"} except HTTPException: raise except Exception as e: raise HTTPException(status_code=500, detail=str(e)) if __name__ == "__main__": import uvicorn uvicorn.run("main:app", host="127.0.0.1", port=8000, reload=True)