Django middleware
Gate whole URL patterns with a small request middleware, or individual views with a decorator.
Install
pip install django orangecheck
Option 1: middleware
# myapp/middleware.py
from django.http import JsonResponse
from orangecheck import check, OrangeCheckError
class OrangeCheckMiddleware:
def __init__(self, get_response):
self.get_response = get_response
def __call__(self, request):
if request.path.startswith("/protected/"):
addr = request.session.get("verified_btc_address")
try:
r = check(addr=addr, min_sats=100_000, min_days=30)
except OrangeCheckError as e:
return JsonResponse({"error": str(e)}, status=500)
if not r.ok:
return JsonResponse(
{"error": "orangecheck", "reasons": list(r.reasons)},
status=403,
)
request.orangecheck = r
return self.get_response(request)
Register in settings.py:
MIDDLEWARE = [
# ... other middleware
"myapp.middleware.OrangeCheckMiddleware",
]
Then in views:
def protected_view(request):
sats = request.orangecheck.sats
return JsonResponse({"sats": sats})
Option 2: decorator
# myapp/decorators.py
from functools import wraps
from django.http import JsonResponse
from orangecheck import check
def require_orangecheck(min_sats=100_000, min_days=30):
def decorator(view_func):
@wraps(view_func)
def wrapper(request, *args, **kwargs):
addr = request.session.get("verified_btc_address")
r = check(addr=addr, min_sats=min_sats, min_days=min_days)
if not r.ok:
return JsonResponse(
{"error": "orangecheck", "reasons": list(r.reasons)},
status=403,
)
request.orangecheck = r
return view_func(request, *args, **kwargs)
return wrapper
return decorator
Usage:
@require_orangecheck(min_sats=100_000, min_days=30)
def protected_view(request):
return JsonResponse({"ok": True})
Signed-challenge auth
For the full "sign in with Bitcoin" flow in Django:
# urls.py
urlpatterns = [
path("auth/challenge", challenge_issue_view),
path("auth/verify", challenge_verify_view),
]
# views.py
from orangecheck import challenge_issue, challenge_verify
def challenge_issue_view(request):
addr = request.GET.get("addr")
c = challenge_issue(addr=addr, audience="https://example.com", purpose="login")
request.session["oc_nonce"] = c.nonce
return JsonResponse({"message": c.message})
def challenge_verify_view(request):
body = json.loads(request.body)
try:
r = challenge_verify(
message=body["message"],
signature=body["signature"],
expected_nonce=request.session.get("oc_nonce"),
expected_audience="https://example.com",
)
except VerificationError as e:
return JsonResponse({"error": str(e)}, status=401)
request.session["verified_btc_address"] = r.address
return JsonResponse({"ok": True, "address": r.address})
Now request.session["verified_btc_address"] is cryptographically proven on subsequent requests.
Async views (Django 4.1+)
Use AsyncClient:
from orangecheck import AsyncClient
oc = AsyncClient()
async def async_protected_view(request):
addr = request.session.get("verified_btc_address")
r = await oc.check(addr=addr, min_sats=100_000)
if not r.ok:
return JsonResponse({"error": r.reasons}, status=403)
return JsonResponse({"sats": r.sats})