Adopt django-allauth with allauth.mfa, giving TOTP, WebAuthn passkeys and recovery codes — and the signup/password-reset flows we'll need next. There was no login UI at all before this (only /admin/), so this brings the auth stack. The critical piece is authentication/adapters.py. A passkey is bound to a WebAuthn Relying Party ID (a domain), and allauth derives that from the request host — which under our subdomain tenancy would bind a passkey to a *single* club (ajax-united.clubmanager.app) and silently fail at every other one. The adapter pins the RP ID to CLUBMANAGER_BASE_DOMAIN so one passkey works across all clubs. Note this cuts both ways: changing that base domain invalidates every existing passkey. RequireMFAMiddleware makes a second factor mandatory for anyone who can change other people's data — Django staff/superusers and holders of an elevated ClubRole (ADMIN/EDITOR), via the access service — while leaving it optional for regular members. /admin/login/ is routed through allauth, since Django's own admin login knows nothing about second factors. allauth is installed WITHOUT django.contrib.sites (optional since allauth 65), so ARCHITECTURE.md's rejection of the Sites framework stands and no Club.site bridge is needed. Sessions are shared across club subdomains, matching the one-passkey-everywhere model; tenancy still scopes what you can see. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
38 lines
1.7 KiB
Plaintext
38 lines
1.7 KiB
Plaintext
# Copy to .env and fill in. Values below are development-friendly defaults.
|
|
|
|
# Required. Generate one, e.g. `python -c "import secrets; print(secrets.token_urlsafe(50))"`.
|
|
DJANGO_SECRET_KEY=change-me
|
|
|
|
# Development toggles.
|
|
DJANGO_DEBUG=True
|
|
|
|
# Hosts Django will serve. `.localhost` matches localhost and any *.localhost
|
|
# subdomain, which the tenant middleware needs for per-club subdomains.
|
|
DJANGO_ALLOWED_HOSTS=.localhost,127.0.0.1,[::1]
|
|
|
|
# Multi-tenancy: subdomains of this base domain resolve to a club by slug,
|
|
# e.g. http://ajax-united.localhost:8000/ -> club with slug "ajax-united".
|
|
# In production set this to your real base domain (e.g. clubmanager.app).
|
|
CLUBMANAGER_BASE_DOMAIN=localhost
|
|
|
|
# Two-factor auth. CLUBMANAGER_BASE_DOMAIN doubles as the WebAuthn Relying Party
|
|
# ID, so ONE passkey works across every club subdomain. Change it and existing
|
|
# passkeys stop validating -- they are cryptographically bound to that domain.
|
|
# CLUBMANAGER_RP_NAME is what the browser shows during a passkey prompt.
|
|
# CLUBMANAGER_RP_NAME=ClubManager
|
|
|
|
# Sessions are shared across club subdomains (log in once, all clubs). Derived
|
|
# from CLUBMANAGER_BASE_DOMAIN in production; left host-only on localhost
|
|
# because browsers reject a Domain attribute there. Override if needed.
|
|
# DJANGO_SESSION_COOKIE_DOMAIN=.clubmanager.app
|
|
# DJANGO_CSRF_COOKIE_DOMAIN=.clubmanager.app
|
|
|
|
# Optional. Defaults to sqlite:///db.sqlite3 for dev; point at Postgres in prod.
|
|
# DJANGO_DATABASE_URL=postgres://user:pass@localhost:5432/clubmanager
|
|
|
|
# Optional. CSRF trusted origins (needed for subdomains in prod), comma-separated.
|
|
# DJANGO_CSRF_TRUSTED_ORIGINS=https://*.clubmanager.app
|
|
|
|
# Optional.
|
|
# DJANGO_TIME_ZONE=Europe/Brussels
|