1416 add support for mfa for tom user logins - #1653
Open
phycodurus wants to merge 31 commits into
Open
Conversation
These docs describe the result django-allauth integration which will be TOM Toolkit 3.1.0
…fa-for-tom-user-logins
django-allauth now answers every /accounts/ page replacing Django's plain LoginView/LogoutView.
This commit deserves a thorough explanation (see also the docstring
in tom_common.adapters): tom_common.adapters is the "customisation
seam" that the docs refer to: a TOM changes account behavior by
subclassing these adapters and re-pointing the configuration variables
like ACCOUNT_ADAPTER / MFA_ADAPTER from settings.py (for example,
re-pointing them to subclasses in `custom_code`).
- TomAccountAdapter.is_open_for_signup: allauth's default is OPEN
self-registration, which the URL cutover briefly inherited; signup is
now closed unless TOM_REGISTRATION_STRATEGY says otherwise (the
strategies themselves land with the registration work).
- TomMFAAdapter: TOTP secrets and recovery-code seeds are stored
encrypted with the SECRET_KEY-derived cipher rather than allauth's
plaintext-in-JSON default, so authenticator data participates in the
same key-rotation story as every EncryptedModelField; the
issuer (the name that appears in authenticator apps) is TOM_NAME;
and can_delete_authenticator enforces the TOM_MFA_REQUIRED policy
(users under the policy cannot remove their second factor —
an administrator still can, in the admin).
- rotate_encryption_key also re-encrypts Authenticator JSON data
('secret', 'seed', 'migrated_codes'), through the adapter so a TOM
with a custom MFA_ADAPTER rotates by its own scheme.
all these template overrides are about getting the django-allauth templates to look like TOM Toolkit. Some specific details worth mentioning: - tom_common/base.html: the new `content_container` block nesting the existing content block distinguished the TOM Toolkit content block from the allauth block that is also named 'content'. A layout that wants to wrap page content needs a block of its own and any plugin layout can use it the same way.
Previously, under AUTH_STRATEGY='LOCKED' the middleware admitted anonymous users only to the login path and OPEN_URLS. This adds the necessary django-allauth URL names to the list. These are the pages that the anonymous users must visit to become not anonymous.
do urlpattern surgery accordingly
When htmx receives a 302 it follows the redirect and swaps the destination page into the requesting fragment — so any authentication redirect fired during an HTMX action (session expiry, the 300 s reauthentication window, a LOCKED bounce) rendered the login page inside a table cell (bad). HTMXRedirectMiddleware rewrites such responses to HX-Redirect, causing HTMX to navigate the whole browser window (good).
The alternative was having this stuff scattered in modules all over tom_common. Now it's in one place. (middleware and signals remain in those modules).
settings introduced: (TOM_MFA_REQUIRED, TOM_PASSWORD_EXPIRY_DAYS, and TOM_REQUIRED_USER_FIELDS). AccountRequirementsMiddleware runs checks for every logged-in request and redirects to the first unmet check's page, with a message naming what to do there. Checks implemented in accounts/requirements.py -- see docstring.
Setting a version string is the signal that invokes the check and makes every user accept the terms before using the TOM.
Allows Administrators to see the requirement status of each user. Also: guardian's AnonymousUser is excluded from the user list. It is a permissions sentinel from django-guardian, not a person; filtered on guardian's ANONYMOUS_USER_NAME setting.
Events and where they are caught: - login success/failure/logout: Django's auth signals. - two-factor authenticator added/removed/reset: allauth's mfa signals. - API-token regeneration: in the view, recording both the token's owner and the acting user (they differ when a superuser regenerates).
This includes the DRF API in the configured authentication requirements. TomTokenAuthentication replaces the REST framework's TokenAuthentication in REST_FRAMEWORK. With neither TOM_API_TOKEN_REQUIRES_MFA nor TOM_API_TOKEN_EXPRIRY_DAYS setting configured, it behaves identically. So, existing TOMs see no change.
With TOM_API_TOKEN_REQUIRES_MFA a token can now only come into existence through a channel protected by the second factor. - api/token-auth/ is served by TomObtainAuthToken, which refuses (403, pointing at the profile page) under the flag. - Token regeneration under the flag is owner-only and requires recent reauthentication via allauth's page. - Raise403Middleware no longer converts /api/ 403s into browser login redirects so scripts can see the 403 JSON response. - The token partial shows created/expires dates (token_expiry_date helper) - The REST API docs name TomTokenAuthentication and the TOM_API_TOKEN_* settings.
The administrator's half of approval_required registration.
4 tasks
Member
Author
|
@rachel3834 The checklist above represents a reasonable tour of the registration/authorization configuration options. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Suggested reviewing path:
==========================================
Do this / See that
Each part adds settings on top of the previous part's.
Prerequisites
python manage.py migrate),runserverup (steps below say :8274 — substitute your port).EMAIL_BACKEND = 'django.core.mail.backends.console.EmailBackend'and
MANAGERS = [('You', 'you@example.com')].adminsuperuser (password known). An authenticator app. A second browser orincognito window ("window B").
curl.Part 0 — baseline (no TOM_* settings configured)
/accounts/login/→ styled login page; no Register button, no "sign up" line, no "Forgot your password?"./accounts/signup/→ "Sign Up Closed" page, tells you to contact the administrators./admin/login/→ redirected to the TOM login page./api-auth/login/→ redirected to the TOM login page.Part 1 — enroll and use 2FA (still no settings)
(no Download button under the default show-once policy; clicking the codes copies them).
without confirming that you have saved your recovery codes?…". Cancel stays; checking the box
then leaving asks nothing. (Closing the tab instead shows the browser's own generic warning.)
/accounts/2fa/→ cards: Authenticator App "active" (Deactivate button), Recovery Codes "10 of 10"./accounts/2fa/now says "9 of 10".tester(with an email address; no 2FA yet).Part 2 — require 2FA
/accounts/2fa/totp/deactivate/→ no Deactivate button; explanation + "Back to two-factor settings" link.Part 3 — terms of service
/terms/accept/, message says accept to continue; placeholder text tells the operator where to write terms.templates/tom_common/partials/terms_of_service_text.htmlin the TOM with your terms → accept page shows them./terms/in a logged-out window → public read-only terms page.(until tester accepts in window B).
'v2'⟳ → prompted again; accept; column header now says (v2)./admin/→ Terms of service acceptances: rows for v1 and v2 with timestamp and IP; read-only.Part 4 — password rules and expiry
alllowercase→ rejected, missing classes listed by name.Part 5 — required profile fields
Part 6 — API tokens
curl -H "Authorization: Token <your-token>" http://localhost:8274/api/targets/→ 200 JSON.curl --request POST http://localhost:8274/api/token-auth/ -H "Content-Type: application/json" -d '{"username":"admin","password":"<pw>"}'→ 403, message points at the profile page.Part 7 — open registration
newuser: form shows username/email/password/name/organization/phone + terms checkbox;the five required fields are marked; "terms of service" in the checkbox label links to
/terms/;submitting without the checkbox → error.
/admin/→ newuser is in the Public group.Part 8 — approval-required registration
applicant→ not logged in; runserver console prints the awaiting-approval mail to MANAGERS.EMAIL_HOST = 'localhost'with the SMTP backend ⟳ →Pending users table warns email is not configured; approving warns you to tell the user directly;
manage.py checkreportstom_common.W002. Restore the console backend ⟳ afterwards.Part 9 — LOCKED + password reset
Part 10 — security log (optional)
Wrap up
python manage.py check→ no warnings (tom_common.W001/W002would name anytoken-authentication or email misconfiguration).