Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
de82704
Add django-allauth[mfa] dependency for MFA and account management
phycodurus Aug 19, 2026
1da9a62
Add accounts & authentication docs as the spec for 3.1 (#1416)
phycodurus Aug 20, 2026
6ceaaee
Merge remote-tracking branch 'origin/dev' into 1416-add-support-for-m…
phycodurus Aug 20, 2026
e9e3d63
Wire django-allauth into the default settings (commit 1/2)
phycodurus Aug 21, 2026
f55a32d
Serve /accounts/ with django-allauth; close the DRF login bypass
phycodurus Aug 21, 2026
5bd3a9c
Route admin logins through allauth; stop eating others' exceptions
phycodurus Aug 21, 2026
a1d51c0
Add TOM account/MFA adapters: signup gate, TOTP encryption, issuer
phycodurus Aug 21, 2026
423a442
Render the allauth pages in the TOM base template with Bootstrap 5
phycodurus Aug 21, 2026
66d0924
Open the authentication-flow pages on LOCKED TOMs
phycodurus Aug 21, 2026
611deee
Make password reset by email opt-in (TOM_PASSWORD_RESET_ENABLED)
phycodurus Aug 21, 2026
96eaeab
Convert 302s on HTMX requests into full-page navigations
phycodurus Aug 21, 2026
4484637
Add the Security card and the user list's two-factor column
phycodurus Aug 21, 2026
336f319
Add Profile.phone_number and timestamp self-chosen passwords
phycodurus Aug 21, 2026
7553294
Collect registration/authentication logic in tom_common.accounts
phycodurus Aug 24, 2026
46b3745
Enforce account requirements per request (TOM_ACCOUNT_REQUIREMENTS)
phycodurus Aug 25, 2026
62668e5
Add the two opt-in password validators
phycodurus Aug 25, 2026
c8eafca
Add terms-of-service acceptance (TOM_TERMS_OF_SERVICE_VERSION)
phycodurus Aug 25, 2026
9b7689d
Show configured requirements as Users-page columns (predicate refactor)
phycodurus Aug 25, 2026
06c3978
Enforce TOM_REQUIRED_USER_FIELDS on the user edit form
phycodurus Aug 25, 2026
c01321e
Log authentication events to the tom_common.security logger
phycodurus Aug 25, 2026
e662a2a
DRF API tokens must obey age, MFA state (TomTokenAuthentication)
phycodurus Aug 25, 2026
996464d
Close the token-issuance paths that bypass the second factor
phycodurus Aug 25, 2026
30fab14
Add self-registration (TOM_REGISTRATION_STRATEGY: open/approval_requi…
phycodurus Aug 26, 2026
b91373d
Add the approval workflow: Pending users table and UserApprovalView
phycodurus Aug 26, 2026
2a2c444
Show Register only while registration is open
phycodurus Aug 26, 2026
a7a2797
Fixes for registration/auth workflow cohesiveness and clarity
phycodurus Aug 27, 2026
391f752
auth/registration should degrade gracefully without email
phycodurus Aug 27, 2026
1cf8d84
update the docs with the latest code changes
phycodurus Aug 27, 2026
d6fa248
Add documentation for Common Accounts Scenarios page
phycodurus Aug 28, 2026
4e6336a
fix save-recovery-codes checkbox; improve confirmatin message
phycodurus Aug 28, 2026
62ee04e
update pyproject.toml et al (resolves merge conflict)
phycodurus Aug 28, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions docs/_static/custom.css
Original file line number Diff line number Diff line change
Expand Up @@ -24,3 +24,11 @@ a.hs {
color: #017792 !important;
font-weight: bold;
}

/* Sphinx's basic.css makes inline-literal tokens (span.pre) no-wrap; a long dotted path in a
table cell then widens its column past the table's declared :widths:. Let literals wrap
inside tables so column hints hold ("anywhere" shrinks min-content width, "break-word" won't). */
table.docutils span.pre {
white-space: normal;
overflow-wrap: anywhere;
}
443 changes: 443 additions & 0 deletions docs/common/authentication.rst

Large diffs are not rendered by default.

100 changes: 100 additions & 0 deletions docs/common/authentication_scenarios.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
Common Accounts Scenarios
=========================

Three worked configurations combining the controls from :doc:`Accounts and Authentication
<authentication>`. Each shows the complete ``settings.py`` recipe and what your users
experience. Settings not shown keep their defaults; every setting is documented in
:doc:`Custom settings <customsettings>`.

A public TOM with optional two-factor authentication
----------------------------------------------------

For a TOM whose pages are public (the default ``READ_ONLY`` strategy) and whose
collaborators get accounts from an administrator. This is the out-of-the-box configuration —
there is nothing to add:

.. code-block:: python

# settings.py: nothing! This is the default behavior.

What you get: anonymous visitors browse but cannot change anything; administrators create
accounts from the *Users* page; every user *may* enable two-factor authentication from the
*Security* card on their profile page, and the *Users* page shows who has. To require MFA on
privileged accounts while leaving it optional otherwise::

TOM_MFA_REQUIRED = 'superusers'

A locked collaboration TOM with self-registration
-------------------------------------------------

For a TOM serving a collaboration whose data is private, but whose membership is broad enough
that administrators should not create every account by hand — new members register themselves
and an administrator approves each one.

.. code-block:: python

AUTH_STRATEGY = 'LOCKED'
TOM_REGISTRATION_STRATEGY = 'approval_required'
TOM_PASSWORD_RESET_ENABLED = True
TOM_REQUIRED_USER_FIELDS = ['first_name', 'last_name', 'email', 'affiliation']

# the notifications this flow sends need working email:
EMAIL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend'
EMAIL_HOST = os.environ['EMAIL_HOST'] # and EMAIL_PORT, EMAIL_USE_TLS, credentials
DEFAULT_FROM_EMAIL = SERVER_EMAIL = os.environ['DEFAULT_FROM_EMAIL']
MANAGERS = [('TOM administrators', os.environ['TOM_ADMIN_EMAIL'])]

What you get: every TOM page requires login (exception: pages required for registration;
the login, sign-up, pending-approval, and password-reset pages are open automatically —
no ``OPEN_URLS`` needed). You also get a *Register* button in the navbar; applicants fill
in the required fields, wait on the "pending approval" page, and ``MANAGERS`` are emailed;
an administrator approves from the *Pending users* table and the new member is emailed a
login link. Without working email the flow still works, but you
must tell applicants yourself — the pages say so, and ``manage.py check`` warns of this
(no email) situation.

A strict hosting policy
-----------------------

For a TOM whose hosting environment mandates more elaborate security: mandatory two-factor
authentication, password composition and expiry rules, terms-of-service acceptance, and
API-token controls.

.. code-block:: python

TOM_MFA_REQUIRED = 'all'
TOM_PASSWORD_EXPIRY_DAYS = 60
TOM_TERMS_OF_SERVICE_VERSION = '2026-09-01' # bump the string to require re-acceptance
TOM_REQUIRED_USER_FIELDS = ['first_name', 'last_name', 'email', 'affiliation', 'phone_number']
TOM_API_TOKEN_EXPIRY_DAYS = 60
TOM_API_TOKEN_REQUIRES_MFA = True

AUTH_PASSWORD_VALIDATORS = [
{'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator'},
{'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator', 'OPTIONS': {'min_length': 12}},
{'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator'},
{'NAME': 'tom_common.accounts.password_validation.CharacterClassValidator'},
{'NAME': 'tom_common.accounts.password_validation.NotSameAsCurrentPasswordValidator'},
]

REST_FRAMEWORK = {
'DEFAULT_AUTHENTICATION_CLASSES': [
'tom_common.accounts.api_auth.TomTokenAuthentication', # API token requires MFA enabled
'rest_framework.authentication.SessionAuthentication',
# no BasicAuthentication: per-request passwords would bypass the second factor
],
'DEFAULT_PERMISSION_CLASSES': ['rest_framework.permissions.IsAuthenticated'],
}

Write your terms of service in ``templates/tom_common/partials/terms_of_service_text.html``.
What you get: after logging in, every user is walked through — in order — accepting the terms,
enrolling an authenticator app, replacing an expired (or administrator-set) password, and
completing missing profile fields, each with a message saying why; then they continue to
wherever they were going. API tokens expire, are honoured only for currently-enrolled users,
and can only be (re)generated by their owner after re-authenticating; the password-only
``/api/token-auth/`` endpoint refuses. The *Users* page shows administrators one compliance
column per control.

Deployment reminders for this profile: a shared cache (rate limits and code replay
protection), ``ALLAUTH_TRUSTED_PROXY_COUNT`` behind a proxy, session-cookie hardening, and a
log destination for ``tom_common.security`` — see :ref:`auth-deployment-notes`.
136 changes: 124 additions & 12 deletions docs/common/customsettings.rst
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,8 @@ Default: []

With an `AUTH_STRATEGY <#auth-strategy>`__ value of **LOCKED**, urls in
this list will remain visible to unauthenticated users. You can also use wild cards to open an entire path.
You might add the homepage (‘/’), for example, or anything with a path that looks like ``'/accounts/reset/*/'``.
You might add the homepage (‘/’), for example, or an API path such as ``'/api/*'`` for scripts that authenticate
with a token. The login, sign-up, second-factor and password-reset pages are open automatically.

`TARGET_PERMISSIONS_ONLY <#target-permissions-only>`__
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Expand Down Expand Up @@ -162,6 +163,71 @@ Default:
Display these columns in the target list table. Values can be attributes or properties on
the Target model, tags or extra fields. See :doc:`Customizing the Target List Table <../targets/target_table>`.

`TOM_ACCOUNT_REQUIREMENTS <#tom-account-requirements>`__
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

Default:

.. code-block:: python

[
'tom_common.accounts.requirements.terms_of_service_accepted',
'tom_common.accounts.requirements.mfa_enrolled',
'tom_common.accounts.requirements.password_not_expired',
'tom_common.accounts.requirements.required_fields_present',
]

Ordered list of checks applied to every request by a logged-in user. Each entry is the dotted path of a check
*function*; its companion ``TOM_*`` setting is the check's *parameter*. A check whose setting is left unconfigured
returns ``None`` immediately (no database queries), so the default list is entirely inactive out of the box:

.. list-table::
:header-rows: 1
:widths: 40 32 28

* - Check function
- Activated by configuring
- Unsatisfied users are sent to
* - ``terms_of_service_accepted``
- ``TOM_TERMS_OF_SERVICE_VERSION``
- the terms acceptance page
* - ``mfa_enrolled``
- ``TOM_MFA_REQUIRED``
- two-factor enrolment
* - ``password_not_expired``
- ``TOM_PASSWORD_EXPIRY_DAYS``
- the change-password page
* - ``required_fields_present``
- ``TOM_REQUIRED_USER_FIELDS``
- their user edit page

The checks run on every request rather than only at login so that a change — a new terms-of-service version, a
password crossing its expiry age, an administrator removing a user's authenticator — takes effect during
long-lived sessions instead of at the next login. Every configured requirement also appears as a column on the
*Users* page so administrators can see who has not yet met it. Add your own check as a dotted path to a function
taking the request and returning ``None`` or the URL name of the page where the user can satisfy the requirement;
a custom check may read its own ``settings.py`` value following the same pattern. See
:doc:`Accounts and Authentication <authentication>`.

`TOM_API_TOKEN_EXPIRY_DAYS <#tom-api-token-expiry-days>`__
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

Default: None

When set to a number of days, API tokens older than that are rejected by ``tom_common.accounts.api_auth.TomTokenAuthentication``
and users must regenerate their token from their profile edit page.

`TOM_API_TOKEN_REQUIRES_MFA <#tom-api-token-requires-mfa>`__
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

Default: False

When ``True``, API tokens are only accepted for users who have enabled two-factor authentication (and only tokens
created after enrolment); the password-only ``/api/token-auth/`` endpoint is disabled and tokens can only be
regenerated by their owner after re-authenticating. Requires ``tom_common.accounts.api_auth.TomTokenAuthentication`` in
``REST_FRAMEWORK['DEFAULT_AUTHENTICATION_CLASSES']`` — ``manage.py check`` warns (``tom_common.W001``) when either
token setting is configured without it.

`TOM_FACILITY_CLASSES <#tom-facility-classes>`__
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

Expand Down Expand Up @@ -200,25 +266,71 @@ A dictionary with the keys being TOM models classes and the values being
the modules that should be used to generate latex tables for those
models.

`TOM_REGISTRATION <#tom-registration>`__
`TOM_MFA_REQUIRED <#tom-mfa-required>`__
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

Default: None

Example:

.. code-block

{
'REGISTRATION_AUTHENTICATION_BACKEND': 'django.contrib.auth.backends.ModelBackend',
'REGISTRATION_REDIRECT_PATTERN': 'home',
'SEND_APPROVAL_EMAILS': True
}
``'all'`` requires every user (including superusers) to enroll an authenticator app before using the TOM;
``'superusers'`` requires it only for superusers. Two-factor authentication is always *available* to users; this
setting only makes it mandatory.

`TOM_NAME <#tom-name>`__
~~~~~~~~~~~~~~~~~~~~~~~~

Default: TOM Toolkit

Set the name of the TOM, used for display purposes such as the navbar
and page titles.
and page titles, and as the issuer shown in authenticator apps.

`TOM_PASSWORD_EXPIRY_DAYS <#tom-password-expiry-days>`__
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

Default: None

When set to a number of days, users whose password is older than that must change it before continuing. A password
set by an administrator counts as expired. Password *rules* are configured with Django's ``AUTH_PASSWORD_VALIDATORS``
(see :doc:`Accounts and Authentication <authentication>`).

`TOM_PASSWORD_RESET_ENABLED <#tom-password-reset-enabled>`__
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

Default: False

Enables password reset by email (``/accounts/password/reset/`` and a *Forgot your password?* link on the login
page). Requires a working ``EMAIL_BACKEND``.

`TOM_REGISTRATION <#tom-registration>`__
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

Deprecated. This dictionary configured the ``tom_registration`` plugin; use ``TOM_REGISTRATION_STRATEGY`` instead
(see :doc:`Updating your TOM <../introduction/updating>`).

`TOM_REGISTRATION_STRATEGY <#tom-registration-strategy>`__
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

Default: None

``None``: only administrators create accounts. ``'open'``: visitors can sign up and are logged in immediately.
``'approval_required'``: visitors can sign up; the account stays inactive until a superuser approves it on the
*Users* page. New users join the ``Public`` group. Notification emails are sent when an email backend is configured.

`TOM_REQUIRED_USER_FIELDS <#tom-required-user-fields>`__
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

Default: []

Example: ``['first_name', 'last_name', 'email', 'affiliation', 'phone_number']``

Fields a user account must have. They are required on the sign-up and user edit forms, and a logged-in user with a
required field missing is taken to their edit page until it is filled in.

`TOM_TERMS_OF_SERVICE_VERSION <#tom-terms-of-service-version>`__
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

Default: None

Example: ``'2026-09-01'``

When set, users must accept the terms of service (the template ``tom_common/partials/terms_of_service_text.html``)
before using the TOM; acceptance of each version is recorded. Change the value to require re-acceptance.
9 changes: 9 additions & 0 deletions docs/common/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ Configuring a TOM

Custom settings <customsettings>
Permissions <permissions>
Accounts and authentication <authentication>
Common accounts scenarios <authentication_scenarios>

TOM Settings
------------
Expand All @@ -33,3 +35,10 @@ Permissions
TOM systems can have hundreds or thousands of users and we recognize that sometimes it is desirable to control
who can access what data or functions. The Toolkit provides fine-grained control over user permissions, as documented
:doc:`here</common/permissions>`.

Accounts and authentication
---------------------------

TOM Toolkit uses `django-allauth <https://docs.allauth.org/en/latest/>`_ to provide login, optional
two-factor authentication, self-registration, password rules, terms of service, profile fields and API
tokens as described :doc:`here</common/authentication>`.
Loading
Loading