infrasynth-backend-kit/infrasynth/security/auth/api_keys.py
2026-08-28 14:38:47 -05:00

49 lines
1.6 KiB
Python

from django.contrib.auth.hashers import check_password
from django.utils import timezone
from rest_framework.authentication import BaseAuthentication
from rest_framework.exceptions import AuthenticationFailed
from ..models import APIKey
class SystemUser:
"""Anonymous system user with scopes as permissions."""
def __init__(self, scopes=None):
self.scopes = scopes or []
self.is_authenticated = True
self.is_superuser = False
self.pk = None
self.id = None
@property
def is_anonymous(self):
return False
def __str__(self):
return f"SystemUser(scopes={self.scopes})"
class APIKeyAuthentication(BaseAuthentication):
"""Service-to-service authentication via X-API-Key header."""
keyword = "X-API-Key"
def authenticate(self, request):
raw_key = request.META.get(f"HTTP_{self.keyword.replace('-', '_').upper()}")
if not raw_key:
return None
try:
prefix, secret = raw_key.split(".", 1)
except ValueError:
raise AuthenticationFailed("Invalid API key format.")
api_key = APIKey.objects.filter(prefix=prefix, is_active=True).first()
if not api_key:
raise AuthenticationFailed("API key not found.")
if not check_password(secret, api_key.key_hash):
raise AuthenticationFailed("Invalid API key.")
if api_key.expires_at and api_key.expires_at < timezone.now():
raise AuthenticationFailed("API key expired.")
api_key.last_used_at = timezone.now()
api_key.save(update_fields=["last_used_at"])
return (SystemUser(scopes=api_key.scopes), api_key)