django / channels

Developer-friendly asynchrony for Django
https://channels.readthedocs.io
BSD 3-Clause "New" or "Revised" License
6.02k stars 793 forks source link

channels.routing.ProtocolTypeRouter not handling raised PermissionDenied #2036

Closed josiahcoad closed 7 months ago

josiahcoad commented 11 months ago

Please also try and include, if you can:

Minimal Code Sample

class JWTAuthMiddleware(BaseMiddleware):
    async def __call__(self, scope, receive, send):
          raise PermissionDenied("Token is invalid or expired")

application = ProtocolTypeRouter({
    "websocket": URLRouter([
                path("ws/device", JWTAuthMiddleware(CameraConsumer.as_asgi())),
      ])
})

Your OS and runtime environment, and browser if applicable

python:3.8-slim

A pip freeze output showing your package versions

channels[daphne]==4.0.0
channels_redis==4.0.0
codeguru_profiler_agent==1.2.4
Django==4.1.10
django-cors-headers==4.0.0
django-health-check==3.17.0
redis[hiredis]==4.4.4

What you expected to happen vs. what actually happened

I have an auth middleware that raises PermissionDenied. I'd expect this to retrun a 401 to client (as it does with http) but it doesn't. Instead the error goes uncaught.

How you're running Channels (runserver? daphne/runworker? Nginx/Apache in front?)

daphene

Console logs and full tracebacks of any errors

Traceback (most recent call last):
  File "/usr/local/lib/python3.8/site-packages/django/contrib/staticfiles/handlers.py", line 101, in __call__
    return await self.application(scope, receive, send)
  File "/usr/local/lib/python3.8/site-packages/channels/routing.py", line 62, in __call__
    return await application(scope, receive, send)
  File "/usr/local/lib/python3.8/site-packages/channels/routing.py", line 116, in __call__
    return await application(
  File "/src/config/middleware.py", line 45, in __call__
    user = await authenticator.authenticate_headers(headers)
  File "/usr/local/lib/python3.8/site-packages/asgiref/sync.py", line 479, in __call__
    ret: _R = await loop.run_in_executor(
  File "/usr/local/lib/python3.8/concurrent/futures/thread.py", line 57, in run
    result = self.fn(*self.args, **self.kwargs)
  File "/usr/local/lib/python3.8/site-packages/asgiref/sync.py", line 538, in thread_handler
    return func(*args, **kwargs)
  File "/src/config/middleware.py", line 34, in authenticate_headers
    raise PermissionDenied("Token is invalid or expired") from e
django.core.exceptions.PermissionDenied: Token is invalid or expired"
carltongibson commented 11 months ago

Yes. Channels doesn't provide a top level exception handler akin to Django's.

Rather than just raising the error you'll need to return the appropriate message and close the connection.