Based on thinking and research, below is a suggestion for handling exempt-paths in turnstile_middleware.py
.
Some nice things about this...
- the regex-compilation happens just once, when the webapp first loads, improving speed.
- it allows us to change a
.env
setting easily, without deploying new code. - it allows the pattern-match code in
turnstile_middleware.py
to be simple, becase regex is handling the "starts-with" or "anywhere-within" check. - the one downside with regex, for folk like me who don't live and breathe it -- is being confident about what's being matched. For that, we have the unit-tests.
export DJANGO_TURNSTILE_EXEMPT_PATHS='[
"^.*?/turnstile-verify/",
"^/static/",
"^/media/"
]'
This shows the flexibility of using regex -- the first example will check for the pattern anywhere, the next two will do a "starts-with" check.
import json
import os
import re
temp_exempts = os.environ.get('DJANGO_TURNSTILE_EXEMPT_PATHS', '[]')
try:
temp_list = json.loads(temp_exempts)
except json.JSONDecodeError:
temp_list = []
TURNSTILE_EXEMPT_PATHS = [re.compile(p) for p in temp_list]
from django.conf import settings
EXEMPT_PATHS = settings.TURNSTILE_EXEMPT_PATHS
## if there's a match...
if any(pattern.match(request.path) for pattern in EXEMPT_PATHS):
## ...just let the request through
return self.get_response(request)
import re
from django.conf import settings
from django.test import SimpleTestCase
class TurnstileExemptPathsTests(SimpleTestCase):
def setUp(self):
## grab the compiled regex list from settings
self.patterns = settings.TURNSTILE_EXEMPT_PATHS
## not a test -- the checker
def is_exempt(self, path: str) -> bool:
return any(p.match(path) for p in self.patterns)
def test_should_be_exempt(self):
## exact path
self.assertTrue(self.is_exempt('/turnstile-verify/'))
## with querystring
self.assertTrue(self.is_exempt('/turnstile-verify/?next=/secret/'))
def test_should_not_be_exempt(self):
self.assertFalse(self.is_exempt('/dashboard/'))
self.assertFalse(self.is_exempt('/api/v1/data/'))
self.assertFalse(self.is_exempt('/turnstile-verify-now/')) # partial non‑match