Quota allocation system: - Replace monthly budget with one-time quota allocation (prepaid model) - Support both adding (+) and deducting (-) quota with underflow protection - Stepped alerts at configurable percentages (e.g., 50%/80%/90%) - Auto-disable when quota exhausted (100%), alert state resets on new allocation - Quota allocation history with operator audit trail IAM management: - Create new IAM sub-accounts directly from AirGate (auto-generates API keys) - SecretKey shown once in dialog with copy-to-clipboard - Attach/detach IAM policies via UI (ArkFullAccess, TOSFullAccess, etc.) - Sync existing users from Volcengine - Project list pulled from Volcengine API for dropdown selection Security & auth: - API Key authentication for external systems (AirDrama integration) - SECRET_KEY enforced in production (raises error if missing with DEBUG=False) - APIKeyUser with proper pk/is_staff attributes for DRF compatibility Infrastructure: - Docker + docker-compose for backend and frontend - Nginx reverse proxy for frontend with /api/ forwarding - Entrypoint with auto-migrate and default admin creation - SQLite data persisted via Docker volume at /app/data/ Bug fixes from audit: - Fix frontend referencing non-existent fields (current_month_spending, effective_budget, budget_usage_percent) - Fix scheduler using naive datetime.now() → timezone.now() - Fix scheduler reading interval from settings instead of GlobalConfig DB - Fix docker-compose SQLite volume mounting as directory - Fix CORS origin with explicit port 80 - Remove dead config (VOLC_ACCESS_KEY/SK, MONITOR_INTERVAL from settings) - Remove unused imports Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
44 lines
1.2 KiB
Python
44 lines
1.2 KiB
Python
from rest_framework.permissions import BasePermission
|
|
from rest_framework.authentication import BaseAuthentication
|
|
from rest_framework.exceptions import AuthenticationFailed
|
|
from django.conf import settings
|
|
|
|
|
|
class APIKeyUser:
|
|
"""Represents an external system authenticated via API Key"""
|
|
pk = None
|
|
id = None
|
|
is_authenticated = True
|
|
is_active = True
|
|
is_staff = False
|
|
is_superuser = False
|
|
username = 'api_key_user'
|
|
|
|
def __str__(self):
|
|
return 'APIKeyUser'
|
|
|
|
|
|
class APIKeyAuthentication(BaseAuthentication):
|
|
"""Authenticate requests via X-API-Key header (for external systems like AirDrama)"""
|
|
|
|
def authenticate(self, request):
|
|
api_key = request.headers.get('X-API-Key', '')
|
|
if not api_key:
|
|
return None # Let other auth methods try
|
|
|
|
expected = settings.AIRGATE_API_KEY
|
|
if not expected:
|
|
return None
|
|
|
|
if api_key != expected:
|
|
raise AuthenticationFailed('API Key 无效')
|
|
|
|
return (APIKeyUser(), None)
|
|
|
|
|
|
class IsAPIKeyAuth(BasePermission):
|
|
"""Permission check: require API Key authentication"""
|
|
|
|
def has_permission(self, request, view):
|
|
return isinstance(request.user, APIKeyUser)
|