Subscription Guard and Installation Guide

This commit is contained in:
ajaysi
2025-10-13 15:27:48 +05:30
parent c38812b6c5
commit b6debd80b7
13 changed files with 1176 additions and 42 deletions

View File

@@ -15,7 +15,7 @@ from services.usage_tracking_service import UsageTrackingService
from services.pricing_service import PricingService
from models.subscription_models import (
APIProvider, SubscriptionPlan, UserSubscription, UsageSummary,
APIProviderPricing, UsageAlert, SubscriptionTier, BillingCycle
APIProviderPricing, UsageAlert, SubscriptionTier, BillingCycle, UsageStatus
)
router = APIRouter(prefix="/api/subscription", tags=["subscription"])

View File

@@ -97,9 +97,6 @@ app.add_middleware(
allow_headers=["*"],
)
# Add API monitoring middleware for subscription enforcement
app.middleware("http")(monitoring_middleware)
# Initialize modular utilities
health_checker = HealthChecker()
rate_limiter = RateLimiter(window_seconds=60, max_requests=200)
@@ -107,17 +104,25 @@ frontend_serving = FrontendServing(app)
router_manager = RouterManager(app)
onboarding_manager = OnboardingManager(app)
# Middleware Order (FastAPI executes in REVERSE order of registration - LIFO):
# Registration order: 1. Monitoring 2. Rate Limit 3. API Key Injection
# Execution order: 1. API Key Injection (sets user_id) 2. Rate Limit 3. Monitoring (uses user_id)
# 1. FIRST REGISTERED (runs LAST) - Monitoring middleware
app.middleware("http")(monitoring_middleware)
# 2. SECOND REGISTERED (runs SECOND) - Rate limiting
@app.middleware("http")
async def rate_limit_middleware(request: Request, call_next):
"""Rate limiting middleware using modular utilities."""
return await rate_limiter.rate_limit_middleware(request, call_next)
# API key injection middleware for production (user-specific keys)
# 3. LAST REGISTERED (runs FIRST) - API key injection
@app.middleware("http")
async def inject_user_api_keys(request: Request, call_next):
"""
Inject user-specific API keys into environment for the request duration.
This allows existing code using os.getenv() to work in production.
Sets request.state.user_id for downstream middleware.
"""
from middleware.api_key_injection_middleware import api_key_injection_middleware
return await api_key_injection_middleware(request, call_next)

View File

@@ -42,6 +42,9 @@ class APIKeyInjectionMiddleware:
# Try different possible keys for user_id
user_id = user.get('user_id') or user.get('clerk_user_id') or user.get('id')
logger.debug(f"[API Key Injection] Extracted user_id: {user_id}")
# Store user_id in request.state for monitoring middleware
request.state.user_id = user_id
except Exception as e:
logger.debug(f"[API Key Injection] Could not extract user from token: {e}")

View File

@@ -466,13 +466,18 @@ async def monitoring_middleware(request: Request, call_next):
# Extract request details - Enhanced user identification
user_id = None
try:
# Check query parameters
if hasattr(request, 'query_params') and 'user_id' in request.query_params:
# PRIORITY 1: Check request.state.user_id (set by API key injection middleware)
if hasattr(request.state, 'user_id') and request.state.user_id:
user_id = request.state.user_id
logger.debug(f"Monitoring: Using user_id from request.state: {user_id}")
# PRIORITY 2: Check query parameters
elif hasattr(request, 'query_params') and 'user_id' in request.query_params:
user_id = request.query_params['user_id']
elif hasattr(request, 'path_params') and 'user_id' in request.path_params:
user_id = request.path_params['user_id']
# Check headers for user identification
# PRIORITY 3: Check headers for user identification
elif 'x-user-id' in request.headers:
user_id = request.headers['x-user-id']
elif 'x-user-email' in request.headers:
@@ -482,22 +487,24 @@ async def monitoring_middleware(request: Request, call_next):
# Check for authorization header with user info
elif 'authorization' in request.headers:
auth_header = request.headers['authorization']
# Extract user info from JWT or other auth tokens if needed
# For now, use a default user for testing
user_id = "default_user"
# Auth middleware should have set request.state.user_id
# If not, skip usage limits (unauthenticated or auth will handle)
user_id = None
logger.debug("Monitoring: Auth header present but no user_id in state - skipping limits")
# For alpha testing, use IP address as user identifier if no other ID found
if not user_id and request.client:
# But only if there's no auth header (truly anonymous)
elif not user_id and request.client and 'authorization' not in request.headers:
user_id = f"alpha_user_{request.client.host}"
# Final fallback for testing
if not user_id:
user_id = "anonymous_user"
# Final fallback: None (skip usage limits for truly anonymous/unauthenticated)
# This prevents false positives for authenticated users
else:
user_id = None
except Exception as e:
logger.debug(f"Error extracting user ID: {e}")
user_id = "error_user"
user_id = None # On error, skip usage limits
# Capture request body for usage tracking (read once, safely)
request_body = None