chore: rebrand ClubManager -> RosterChief, add lucide icons
The clubmanager.app domain was taken, so the platform is now RosterChief (rosterchief.app). Renames the Django project package clubmanager/ -> rosterchief/ (git tracks it as a move, so history follows), every `from rosterchief.base import ...`, the settings/wsgi/asgi module paths, env vars (ROSTERCHIEF_BASE_DOMAIN / ROSTERCHIEF_RP_NAME), the MFA adapter (RosterChiefMFAAdapter), brand text, and the docs. Two things were deliberately NOT swept: - club.models.ClubManager stays: it is the Django manager *for Club*, not the brand. A blind rename would have silently broken it. - Migrations are untouched (history is not rewritten). The only reference was a cosmetic help_text, so a normal AlterField migration carries the new domain. Note the WebAuthn RP ID is the base domain, so moving to rosterchief.app cryptographically invalidates any passkey enrolled under the old one; they cannot be migrated and must be re-enrolled. Nothing is in production, so the real cost is zero. Add django-lucide (from bsiebens/lucide) for icons: the theme toggle now swaps sun/moon against the effective theme, and the control panel gets icons on its tabs, actions and stat groups. Its classifiers stop at Django 5.0, but that is stale metadata — verified rendering on Django 6 / Python 3.14. Also add formbuilder, shop and controlpanel to ruff's known-first-party list, which had drifted behind the apps that landed. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -2,11 +2,11 @@
|
||||
|
||||
The MFA adapter exists for one important reason: WebAuthn credentials are bound
|
||||
to a **Relying Party ID** (a domain). allauth's default RP ID is the request's
|
||||
host — which under our subdomain tenancy would be ``ajax-united.clubmanager.app``,
|
||||
host — which under our subdomain tenancy would be ``ajax-united.rosterchief.app``,
|
||||
binding a passkey to *one club*. A member of two clubs would then need two
|
||||
passkeys, and a credential registered at one club would silently fail at another.
|
||||
|
||||
Pinning the RP ID to the registrable parent domain (``clubmanager.app``) makes a
|
||||
Pinning the RP ID to the registrable parent domain (``rosterchief.app``) makes a
|
||||
single passkey work across every club subdomain.
|
||||
"""
|
||||
|
||||
@@ -14,7 +14,7 @@ from allauth.mfa.adapter import DefaultMFAAdapter
|
||||
from django.conf import settings
|
||||
|
||||
|
||||
class ClubManagerMFAAdapter(DefaultMFAAdapter):
|
||||
class RosterChiefMFAAdapter(DefaultMFAAdapter):
|
||||
def get_public_key_credential_rp_entity(self) -> dict[str, str]:
|
||||
return {
|
||||
"id": webauthn_rp_id(),
|
||||
@@ -28,7 +28,7 @@ def webauthn_rp_id() -> str:
|
||||
Falls back to the request host when no base domain is configured (e.g. a
|
||||
bare ``localhost`` dev server), which keeps WebAuthn usable there.
|
||||
"""
|
||||
base_domain = getattr(settings, "CLUBMANAGER_BASE_DOMAIN", "")
|
||||
base_domain = getattr(settings, "ROSTERCHIEF_BASE_DOMAIN", "")
|
||||
if base_domain:
|
||||
return base_domain
|
||||
|
||||
|
||||
@@ -13,7 +13,7 @@ from django.urls import reverse
|
||||
from club.models import Club, ClubRole
|
||||
from members.models import Member
|
||||
|
||||
from .adapters import ClubManagerMFAAdapter, webauthn_rp_id
|
||||
from .adapters import RosterChiefMFAAdapter, webauthn_rp_id
|
||||
from .middleware import RequireMFAMiddleware, mfa_required_for
|
||||
|
||||
User = get_user_model()
|
||||
@@ -104,9 +104,9 @@ class UserModelTests(TestCase):
|
||||
|
||||
|
||||
@override_settings(
|
||||
CLUBMANAGER_BASE_DOMAIN="clubmanager.app",
|
||||
MFA_WEBAUTHN_RP_NAME="ClubManager",
|
||||
ALLOWED_HOSTS=[".clubmanager.app", "example.test"],
|
||||
ROSTERCHIEF_BASE_DOMAIN="rosterchief.app",
|
||||
MFA_WEBAUTHN_RP_NAME="RosterChief",
|
||||
ALLOWED_HOSTS=[".rosterchief.app", "example.test"],
|
||||
)
|
||||
class WebAuthnRelyingPartyTests(TestCase):
|
||||
"""A passkey is bound to a Relying Party ID (a domain).
|
||||
@@ -119,22 +119,22 @@ class WebAuthnRelyingPartyTests(TestCase):
|
||||
def rp_entity(self, host):
|
||||
request = RequestFactory().get("/", HTTP_HOST=host)
|
||||
with context.request_context(request):
|
||||
return ClubManagerMFAAdapter().get_public_key_credential_rp_entity()
|
||||
return RosterChiefMFAAdapter().get_public_key_credential_rp_entity()
|
||||
|
||||
def test_rp_id_is_the_parent_domain_not_the_club_subdomain(self):
|
||||
self.assertEqual(self.rp_entity("ajax-united.clubmanager.app")["id"], "clubmanager.app")
|
||||
self.assertEqual(self.rp_entity("ajax-united.rosterchief.app")["id"], "rosterchief.app")
|
||||
|
||||
def test_rp_id_is_identical_across_clubs(self):
|
||||
# The whole point: a passkey registered at one club works at the others.
|
||||
here = self.rp_entity("ajax-united.clubmanager.app")
|
||||
there = self.rp_entity("rival-fc.clubmanager.app")
|
||||
here = self.rp_entity("ajax-united.rosterchief.app")
|
||||
there = self.rp_entity("rival-fc.rosterchief.app")
|
||||
|
||||
self.assertEqual(here["id"], there["id"])
|
||||
|
||||
def test_rp_name_comes_from_settings(self):
|
||||
self.assertEqual(self.rp_entity("ajax-united.clubmanager.app")["name"], "ClubManager")
|
||||
self.assertEqual(self.rp_entity("ajax-united.rosterchief.app")["name"], "RosterChief")
|
||||
|
||||
@override_settings(CLUBMANAGER_BASE_DOMAIN="")
|
||||
@override_settings(ROSTERCHIEF_BASE_DOMAIN="")
|
||||
def test_falls_back_to_the_request_host_without_a_base_domain(self):
|
||||
request = RequestFactory().get("/", HTTP_HOST="example.test:8000")
|
||||
|
||||
|
||||
Reference in New Issue
Block a user