Refactor API permissions and throttling: Updated default permission classes to enforce authentication and tenant access. Introduced new permission for system accounts and developers. Enhanced throttling rates for various operations to reduce false 429 errors. Improved API key loading logic to prioritize account-specific settings, with fallbacks to system accounts and Django settings. Updated integration views and sidebar to reflect new permission structure.

This commit is contained in:
IGNY8 VPS (Salman)
2025-12-07 17:23:42 +00:00
parent 3cbed65601
commit 65fea95d33
15 changed files with 374 additions and 71 deletions

View File

@@ -43,32 +43,48 @@ class AICore:
self._load_account_settings()
def _load_account_settings(self):
"""Load API keys and model from IntegrationSettings or Django settings"""
if self.account:
"""Load API keys from IntegrationSettings with fallbacks (account -> system account -> Django settings)"""
def get_system_account():
try:
from igny8_core.auth.models import Account
for slug in ['aws-admin', 'default-account', 'default']:
acct = Account.objects.filter(slug=slug).first()
if acct:
return acct
except Exception:
return None
return None
def get_integration_key(integration_type: str, account):
if not account:
return None
try:
from igny8_core.modules.system.models import IntegrationSettings
# Load OpenAI settings
openai_settings = IntegrationSettings.objects.filter(
integration_type='openai',
account=self.account,
settings_obj = IntegrationSettings.objects.filter(
integration_type=integration_type,
account=account,
is_active=True
).first()
if openai_settings and openai_settings.config:
self._openai_api_key = openai_settings.config.get('apiKey')
# Load Runware settings
runware_settings = IntegrationSettings.objects.filter(
integration_type='runware',
account=self.account,
is_active=True
).first()
if runware_settings and runware_settings.config:
self._runware_api_key = runware_settings.config.get('apiKey')
if settings_obj and settings_obj.config:
return settings_obj.config.get('apiKey')
except Exception as e:
logger.warning(f"Could not load account settings: {e}", exc_info=True)
# Fallback to Django settings for API keys only (no model fallback)
logger.warning(f"Could not load {integration_type} settings for account {getattr(account, 'id', None)}: {e}", exc_info=True)
return None
# 1) Account-specific keys
if self.account:
self._openai_api_key = get_integration_key('openai', self.account)
self._runware_api_key = get_integration_key('runware', self.account)
# 2) Fallback to system account keys (shared across tenants)
if not self._openai_api_key or not self._runware_api_key:
system_account = get_system_account()
if not self._openai_api_key:
self._openai_api_key = get_integration_key('openai', system_account)
if not self._runware_api_key:
self._runware_api_key = get_integration_key('runware', system_account)
# 3) Fallback to Django settings
if not self._openai_api_key:
self._openai_api_key = getattr(settings, 'OPENAI_API_KEY', None)
if not self._runware_api_key:

View File

@@ -160,3 +160,21 @@ class IsAdminOrOwner(permissions.BasePermission):
return False
class IsSystemAccountOrDeveloper(permissions.BasePermission):
"""
Allow only system accounts (aws-admin/default-account/default) or developer role.
Use for sensitive, globally-scoped settings like integration API keys.
"""
def has_permission(self, request, view):
user = getattr(request, "user", None)
if not user or not user.is_authenticated:
return False
account_slug = getattr(getattr(user, "account", None), "slug", None)
if user.role == "developer":
return True
if account_slug in ["aws-admin", "default-account", "default"]:
return True
return False

View File

@@ -41,9 +41,11 @@ class DebugScopedRateThrottle(ScopedRateThrottle):
if not request.user or not hasattr(request.user, 'is_authenticated') or not request.user.is_authenticated:
public_blueprint_bypass = True
# Bypass for system account users (aws-admin, default-account, etc.)
# Bypass for authenticated users (avoid user-facing 429s) and system accounts
system_account_bypass = False
authenticated_bypass = False
if hasattr(request, 'user') and request.user and hasattr(request.user, 'is_authenticated') and request.user.is_authenticated:
authenticated_bypass = True # Do not throttle logged-in users
try:
# Check if user is in system account (aws-admin, default-account, default)
if hasattr(request.user, 'is_system_account_user') and request.user.is_system_account_user():
@@ -55,7 +57,7 @@ class DebugScopedRateThrottle(ScopedRateThrottle):
# If checking fails, continue with normal throttling
pass
if debug_bypass or env_bypass or system_account_bypass or public_blueprint_bypass:
if debug_bypass or env_bypass or system_account_bypass or public_blueprint_bypass or authenticated_bypass:
# In debug mode or for system accounts, still set throttle headers but don't actually throttle
# This allows testing throttle headers without blocking requests
if hasattr(self, 'get_rate'):

View File

@@ -10,7 +10,7 @@ from drf_spectacular.utils import extend_schema, extend_schema_view
from igny8_core.api.base import AccountModelViewSet
from igny8_core.api.response import success_response, error_response
from igny8_core.api.throttles import DebugScopedRateThrottle
from igny8_core.api.permissions import IsAuthenticatedAndActive, HasTenantAccess, IsAdminOrOwner
from igny8_core.api.permissions import IsAuthenticatedAndActive, HasTenantAccess, IsSystemAccountOrDeveloper
from django.conf import settings
logger = logging.getLogger(__name__)
@@ -30,7 +30,7 @@ class IntegrationSettingsViewSet(viewsets.ViewSet):
Following reference plugin pattern: WordPress uses update_option() for igny8_api_settings
We store in IntegrationSettings model with account isolation
"""
permission_classes = [IsAuthenticatedAndActive, HasTenantAccess, IsAdminOrOwner]
permission_classes = [IsAuthenticatedAndActive, HasTenantAccess, IsSystemAccountOrDeveloper]
throttle_scope = 'system_admin'
throttle_classes = [DebugScopedRateThrottle]

View File

@@ -214,7 +214,8 @@ REST_FRAMEWORK = {
'rest_framework.filters.OrderingFilter',
],
'DEFAULT_PERMISSION_CLASSES': [
'rest_framework.permissions.AllowAny', # Allow unauthenticated access for now
'igny8_core.api.permissions.IsAuthenticatedAndActive',
'igny8_core.api.permissions.HasTenantAccess',
],
'DEFAULT_AUTHENTICATION_CLASSES': [
'igny8_core.api.authentication.APIKeyAuthentication', # WordPress API key authentication (check first)
@@ -232,33 +233,33 @@ REST_FRAMEWORK = {
'igny8_core.api.throttles.DebugScopedRateThrottle',
],
'DEFAULT_THROTTLE_RATES': {
# AI Functions - Expensive operations
'ai_function': '10/min', # AI content generation, clustering
'image_gen': '15/min', # Image generation
# AI Functions - Expensive operations (kept modest but higher to reduce false 429s)
'ai_function': '60/min',
'image_gen': '90/min',
# Content Operations
'content_write': '30/min', # Content creation, updates
'content_read': '100/min', # Content listing, retrieval
'content_write': '180/min',
'content_read': '600/min',
# Authentication
'auth': '20/min', # Login, register, password reset
'auth_strict': '5/min', # Sensitive auth operations
'auth_read': '120/min', # Read-only auth-adjacent endpoints (e.g., subscriptions)
'auth': '300/min', # Login, register, password reset
'auth_strict': '120/min', # Sensitive auth operations
'auth_read': '600/min', # Read-only auth-adjacent endpoints (e.g., subscriptions, industries)
# Planner Operations
'planner': '60/min', # Keyword, cluster, idea operations
'planner_ai': '10/min', # AI-powered planner operations
'planner': '300/min',
'planner_ai': '60/min',
# Writer Operations
'writer': '60/min', # Task, content management
'writer_ai': '10/min', # AI-powered writer operations
'writer': '300/min',
'writer_ai': '60/min',
# System Operations
'system': '100/min', # Settings, prompts, profiles
'system_admin': '30/min', # Admin-only system operations
'system': '600/min',
'system_admin': '120/min',
# Billing Operations
'billing': '30/min', # Credit queries, usage logs
'billing_admin': '10/min', # Credit management (admin)
'linker': '30/min', # Content linking operations
'optimizer': '10/min', # AI-powered optimization
'integration': '100/min', # Integration operations (WordPress, etc.)
'billing': '180/min',
'billing_admin': '60/min',
'linker': '180/min',
'optimizer': '60/min',
'integration': '600/min',
# Default fallback
'default': '100/min', # Default for endpoints without scope
'default': '600/min',
},
# OpenAPI Schema Generation (drf-spectacular)
'DEFAULT_SCHEMA_CLASS': 'drf_spectacular.openapi.AutoSchema',