feat(throttling): add auth throttling and structured cooldown errors
This commit is contained in:
@@ -1,11 +1,14 @@
|
||||
import logging
|
||||
import traceback
|
||||
from collections.abc import Iterable
|
||||
from datetime import timedelta
|
||||
from typing import Any
|
||||
|
||||
from django.conf import settings
|
||||
from django.utils import timezone
|
||||
from rest_framework import status as http_status
|
||||
from rest_framework.exceptions import ErrorDetail
|
||||
from rest_framework.exceptions import Throttled
|
||||
from rest_framework.response import Response
|
||||
from rest_framework.views import exception_handler as drf_exception_handler
|
||||
|
||||
@@ -83,7 +86,36 @@ def exception_handler(exc, context) -> Response:
|
||||
if status_code < 500:
|
||||
messages = _to_str_list(detail)
|
||||
payload = _format_payload(messages, status_code)
|
||||
return Response(payload, status=status_code)
|
||||
if isinstance(exc, Throttled):
|
||||
request = context.get("request")
|
||||
wait = getattr(exc, "wait", None)
|
||||
retry_after_seconds = None
|
||||
if wait is not None:
|
||||
retry_after_seconds = max(int(wait), 0)
|
||||
elif request is not None:
|
||||
retry_after_seconds = getattr(request, "_retry_after_seconds", None)
|
||||
throttle_scope = getattr(request, "_throttle_scope", None) if request else None
|
||||
payload.update(
|
||||
{
|
||||
"code": "throttled",
|
||||
"scope": throttle_scope,
|
||||
"retry_after_seconds": retry_after_seconds,
|
||||
"throttled_until": (
|
||||
timezone.now() + timedelta(seconds=retry_after_seconds)
|
||||
).isoformat()
|
||||
if retry_after_seconds is not None
|
||||
else None,
|
||||
}
|
||||
)
|
||||
formatted_response = Response(payload, status=status_code)
|
||||
for header, value in response.headers.items():
|
||||
formatted_response[header] = value
|
||||
if isinstance(exc, Throttled) and "Retry-After" not in formatted_response:
|
||||
request = context.get("request")
|
||||
retry_after_seconds = getattr(request, "_retry_after_seconds", None) if request else None
|
||||
if retry_after_seconds is not None:
|
||||
formatted_response["Retry-After"] = str(max(int(retry_after_seconds), 0))
|
||||
return formatted_response
|
||||
|
||||
traceback_text = traceback.format_exc()
|
||||
payload = _format_payload(
|
||||
|
||||
Reference in New Issue
Block a user