bottleneckcrm/backend/common/external_auth.py

65 lines
2.2 KiB
Python

import logging
from drf_spectacular.extensions import OpenApiAuthenticationExtension
from rest_framework.authentication import BaseAuthentication
from rest_framework.exceptions import AuthenticationFailed
from common.models import Org, Profile
logger = logging.getLogger(__name__)
class APIKeyAuthentication(BaseAuthentication):
"""
API Key authentication for programmatic/external access.
Authenticates requests with 'Token' header containing an organization API key.
This is used for service-to-service or programmatic access to the API.
Usage:
curl -H "Token: <org_api_key>" https://api.example.com/endpoint/
"""
def authenticate(self, request):
api_key = request.headers.get("Token")
if not api_key:
return None # Let other auth classes handle this request
try:
organization = Org.objects.get(api_key=api_key, is_active=True)
# Get an admin profile for this org to act as the authenticated user
profile = Profile.objects.filter(
org=organization, role="ADMIN", is_active=True
).first()
if not profile:
logger.error(f"No active admin profile found for org {organization.id}")
raise AuthenticationFailed("Invalid API Key configuration")
# Set org context on request for downstream use
request.profile = profile
request.org = organization
request.META["org"] = str(organization.id)
logger.debug(f"API key authenticated: org={organization.id}")
return (profile.user, None)
except Org.DoesNotExist:
logger.warning("Invalid API key attempted")
raise AuthenticationFailed("Invalid API Key")
class APIKeyAuthenticationScheme(OpenApiAuthenticationExtension):
"""OpenAPI schema extension for API Key authentication."""
target_class = "common.external_auth.APIKeyAuthentication"
name = "APIKeyAuth"
def get_security_definition(self, auto_schema):
return {
"type": "apiKey",
"in": "header",
"name": "Token",
"description": "Organization API key for programmatic access",
}