Skip to content

Commit 5d63a15

Browse files
committed
fix: make middleware async-compatible to fix asgi
1 parent fe9266d commit 5d63a15

File tree

5 files changed

+86
-61
lines changed

5 files changed

+86
-61
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
.venv
2+
__pycache__
23
.env
34
.env.local
45
.static/**/*

hub/middleware.py

Lines changed: 81 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,32 +1,99 @@
1+
import logging
12
from datetime import timedelta
3+
from inspect import isawaitable
24

5+
from django.http import HttpRequest
6+
from django.utils.decorators import sync_and_async_middleware
37
from django.utils.timezone import now
48

9+
from asgiref.sync import iscoroutinefunction, sync_to_async
10+
from gqlauth.core.middlewares import USER_OR_ERROR_KEY, UserOrError
11+
from gqlauth.core.middlewares import django_jwt_middleware as _django_jwt_middleware
12+
from gqlauth.core.types_ import GQLAuthError, GQLAuthErrors
13+
from whitenoise.middleware import WhiteNoiseMiddleware
14+
515
from hub.models import UserProperties
616

17+
logger = logging.getLogger(__name__)
718

8-
class RecordLastSeenMiddleware:
9-
one_day = timedelta(hours=24)
1019

11-
def __init__(self, get_response):
12-
self.get_response = get_response
20+
@sync_and_async_middleware
21+
def record_last_seen_middleware(get_response):
22+
one_day = timedelta(hours=24)
1323

14-
def __call__(self, request):
24+
def process_request(request):
1525
if request.user.is_authenticated:
1626
user = request.user
17-
if not hasattr(user, "userproperties"):
18-
UserProperties.objects.create(user=user)
19-
27+
props = UserProperties.objects.get_or_create(user=user)
2028
last_seen = request.session.get("last_seen", None)
21-
22-
yesterday = now().replace(hour=0, minute=0) - self.one_day
23-
29+
yesterday = now().replace(hour=0, minute=0) - one_day
2430
if last_seen is None or last_seen < yesterday.timestamp():
25-
props = user.userproperties
2631
props.last_seen = now()
2732
request.session["last_seen"] = props.last_seen.timestamp()
2833
props.save()
2934

30-
response = self.get_response(request)
35+
if iscoroutinefunction(get_response):
36+
async def middleware(request: HttpRequest):
37+
await sync_to_async(process_request)(request)
38+
return await get_response(request)
39+
40+
else:
41+
def middleware(request: HttpRequest):
42+
process_request(request)
43+
return get_response(request)
44+
45+
return middleware
46+
47+
48+
@sync_and_async_middleware
49+
def async_whitenoise_middleware(get_response):
50+
def logic(request):
51+
return WhiteNoiseMiddleware(get_response)(request)
52+
53+
if iscoroutinefunction(get_response):
54+
async def middleware(request: HttpRequest):
55+
response = logic(request)
56+
if isawaitable(response):
57+
response = await response
58+
return response
59+
60+
else:
61+
def middleware(request: HttpRequest):
62+
return logic(request)
63+
64+
return middleware
65+
66+
67+
@sync_and_async_middleware
68+
def django_jwt_middleware(get_response):
69+
"""
70+
Wrap the gqlauth jwt middleware in an exception
71+
handler (initially added because if a user is
72+
deleted, the middleware throws an error,
73+
causing a 500 instead of a 403).
74+
"""
75+
gqlauth_middleware = _django_jwt_middleware(get_response)
76+
77+
def exception_handler(error: Exception, request: HttpRequest):
78+
logger.warning(f"Gqlauth middleware error: {error}")
79+
user_or_error = UserOrError()
80+
user_or_error.error = GQLAuthError(code=GQLAuthErrors.UNAUTHENTICATED)
81+
setattr(request, USER_OR_ERROR_KEY, user_or_error)
82+
83+
if iscoroutinefunction(get_response):
84+
async def middleware(request: HttpRequest):
85+
try:
86+
return await gqlauth_middleware(request)
87+
except Exception:
88+
exception_handler(request)
89+
return await get_response(request)
90+
91+
else:
92+
def middleware(request: HttpRequest):
93+
try:
94+
return gqlauth_middleware(request)
95+
except Exception as e:
96+
exception_handler(request)
97+
return get_response(request)
3198

32-
return response
99+
return middleware

local_intelligence_hub/settings.py

Lines changed: 3 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,6 @@
2929
DEBUG=(bool, False),
3030
ALLOWED_HOSTS=(list, []),
3131
CORS_ALLOWED_ORIGINS=(list, ['http://localhost:3000']),
32-
HIDE_DEBUG_TOOLBAR=(bool, False),
3332
GOOGLE_ANALYTICS=(str, ""),
3433
GOOGLE_SITE_VERIFICATION=(str, ""),
3534
TEST_AIRTABLE_BASE_ID=(str, ""),
@@ -50,7 +49,6 @@
5049
ALLOWED_HOSTS = env("ALLOWED_HOSTS")
5150
CORS_ALLOWED_ORIGINS = env("CORS_ALLOWED_ORIGINS")
5251
CACHE_FILE = env("CACHE_FILE")
53-
HIDE_DEBUG_TOOLBAR = env("HIDE_DEBUG_TOOLBAR")
5452
MAPIT_URL = env("MAPIT_URL")
5553
MAPIT_API_KEY = env("MAPIT_API_KEY")
5654
GOOGLE_ANALYTICS = env("GOOGLE_ANALYTICS")
@@ -105,16 +103,16 @@
105103

106104
MIDDLEWARE = [
107105
"django.middleware.security.SecurityMiddleware",
108-
"whitenoise.middleware.WhiteNoiseMiddleware",
106+
"hub.middleware.async_whitenoise_middleware",
109107
"django.contrib.sessions.middleware.SessionMiddleware",
110108
"corsheaders.middleware.CorsMiddleware",
111109
"django.middleware.common.CommonMiddleware",
112110
"django.middleware.csrf.CsrfViewMiddleware",
113111
"django.contrib.auth.middleware.AuthenticationMiddleware",
114-
"gqlauth.core.middlewares.django_jwt_middleware",
112+
"hub.middleware.django_jwt_middleware",
115113
"django.contrib.messages.middleware.MessageMiddleware",
116114
"django.middleware.clickjacking.XFrameOptionsMiddleware",
117-
"hub.middleware.RecordLastSeenMiddleware",
115+
"hub.middleware.record_last_seen_middleware",
118116
]
119117

120118
ROOT_URLCONF = "local_intelligence_hub.urls"
@@ -235,31 +233,6 @@
235233
EMAIL_PORT = env.str("EMAIL_PORT", 1025)
236234
DEFAULT_FROM_EMAIL = env.str("DEFAULT_FROM_EMAIL", "webmaster@localhost")
237235

238-
if DEBUG and HIDE_DEBUG_TOOLBAR is False: # pragma: no cover
239-
hostname, _, ips = socket.gethostbyname_ex(socket.gethostname())
240-
INTERNAL_IPS = [ip[:-1] + "1" for ip in ips] + ["127.0.0.1", "10.0.2.2"]
241-
CSRF_TRUSTED_ORIGINS = ["https://*.preview.app.github.dev"]
242-
243-
# debug toolbar has to come after django_hosts middleware
244-
MIDDLEWARE.insert(1, "debug_toolbar.middleware.DebugToolbarMiddleware")
245-
246-
INSTALLED_APPS += ("debug_toolbar",)
247-
248-
DEBUG_TOOLBAR_PANELS = [
249-
"debug_toolbar.panels.versions.VersionsPanel",
250-
"debug_toolbar.panels.timer.TimerPanel",
251-
"debug_toolbar.panels.settings.SettingsPanel",
252-
"debug_toolbar.panels.headers.HeadersPanel",
253-
"debug_toolbar.panels.request.RequestPanel",
254-
"debug_toolbar.panels.sql.SQLPanel",
255-
"debug_toolbar.panels.staticfiles.StaticFilesPanel",
256-
"debug_toolbar.panels.templates.TemplatesPanel",
257-
"debug_toolbar.panels.cache.CachePanel",
258-
"debug_toolbar.panels.signals.SignalsPanel",
259-
"debug_toolbar.panels.logging.LoggingPanel",
260-
"debug_toolbar.panels.redirects.RedirectsPanel",
261-
]
262-
263236
POSTCODES_IO_URL = "https://postcodes.commonknowledge.coop"
264237
POSTCODES_IO_BATCH_MAXIMUM = 100
265238

poetry.lock

Lines changed: 1 addition & 16 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

pyproject.toml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,6 @@ strawberry-graphql = {extras = ["asgi"], version = "^0.220.0"}
3737
pandas = "^2.2.1"
3838

3939
[tool.poetry.dev-dependencies]
40-
django-debug-toolbar = "^3.7.0"
4140
black = "^22.8.0"
4241
coverage = "^6.5.0"
4342
flake8 = "^5.0.4"

0 commit comments

Comments
 (0)