Files
RosterChief/authentication/middleware.py
Bernard Siebens adf1120358 Checkpoint: management app redesign, onboarding/signup workflow, and events calendar backend
Large uncommitted body of work accumulated across sessions on this branch --
committing as a checkpoint so it's tracked and future worktree-isolated agents
see the real codebase instead of a stale ancestor commit. Covers the
management app's dedicated Tailwind theme and templates, the club onboarding
requirement/signup workflow (club/services/onboarding.py, requirement/status
models, sign-up dashboard), fee/status auto-activation decoupling, referee
management, and the new events calendar grid service layer.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ECGMEwrc2k4D8VQuwjstj9
2026-08-19 23:34:43 +02:00

50 lines
1.9 KiB
Python

"""Force MFA enrolment for privileged users.
Anyone who can change other people's data must have a second factor: Django
staff/superusers, and anyone holding an elevated ``ClubRole`` (ADMIN, EDITOR, or
MEMBER_ADMIN) in *any* club. Regular members may enrol, but aren't forced to.
Enrolled users are challenged for their second factor by allauth at login; this
middleware only handles the other half — a privileged user who has never
enrolled is redirected to the MFA setup page until they do.
"""
from allauth.mfa.utils import is_mfa_enabled
from django.conf import settings
from django.shortcuts import redirect
from django.urls import reverse
from club.models import ClubRole
#: Paths a not-yet-enrolled user must still reach (to enrol, or to log out).
#: ``/__reload__/`` is django-browser-reload's event stream, which only exists
#: under DEBUG — without it, live reload dies on the enrolment page itself.
EXEMPT_PREFIXES = ("/accounts/", "/static/", "/media/", "/__reload__/")
ELEVATED_ROLES = (ClubRole.Roles.ADMIN, ClubRole.Roles.EDITOR, ClubRole.Roles.MEMBER_ADMIN)
def mfa_required_for(user) -> bool:
"""Privileged users must hold a second factor."""
if user.is_staff or user.is_superuser:
return True
return ClubRole.objects.filter(member__user=user, role__in=ELEVATED_ROLES).exists()
class RequireMFAMiddleware:
def __init__(self, get_response):
self.get_response = get_response
def __call__(self, request):
if self.needs_enrolment(request):
return redirect(reverse(settings.MFA_ENROLMENT_URL_NAME))
return self.get_response(request)
def needs_enrolment(self, request) -> bool:
user = getattr(request, "user", None)
if user is None or not user.is_authenticated:
return False
if request.path.startswith(EXEMPT_PREFIXES):
return False
return mfa_required_for(user) and not is_mfa_enabled(user)