Conversation
…irements - Enhanced security documentation to clarify compromised session handling and step-up re-authentication requirements. - Updated settings documentation to include `MFA_STEPUP_MAX_AGE` and its interaction with `MFA_REMEMBER_MY_BROWSER`. - Added detailed explanation of `MFA_STEPUP_MAX_AGE` functionality and its implications for factor changes. chore: upgrade version to 4.2.0 and update dependencies - Bumped version from 4.1.0 to 4.2.0 in pyproject.toml and uv.lock. - Updated development dependencies to include `django-otp` and `django-mfa2`. - Ensured compatibility with Django 5.2 and added necessary test configurations. fix: enforce recent challenge for factor changes - Implemented a mechanism to require a recent factor challenge before allowing changes to enrolled factors. - Redirect users to the verification process if their last challenge was more than 300 seconds ago. docs: clarify MFA_SECRET_ENCRYPTION_KEYS functionality - Corrected documentation to specify that `MFA_SECRET_ENCRYPTION_KEYS` signs TOTP secrets rather than encrypting them.
There was a problem hiding this comment.
Pull request overview
This PR releases django-mfa 4.2.0 by adding step-up re-authentication for factor-mutating actions, expanding operational tooling (new management commands + importers), and updating documentation/settings to describe the new security model and migrations.
Changes:
- Add step-up re-authentication (
mfa_recent_required/MfaRecentRequiredMixin,MFA_STEPUP_MAX_AGE, freshness tracking via sessionat) and apply it to django-mfa’s factor management/enrollment views. - Add operational features:
MfaExemptionmodel +mfa_disablecommand +mfa_exemption_changedsignal, plusmfa_status,mfa_reset,mfa_report, and importers for django-otp / django-mfa2. - Update docs/changelog, bump version to 4.2.0, and adjust tests/runner + dev deps to cover importer fixtures and new behavior.
Reviewed changes
Copilot reviewed 44 out of 47 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
| uv.lock | Bump project version and lock new dev dependencies (django-otp, django-mfa2) plus transitive packages. |
| pyproject.toml | Version bump to 4.2.0 and add dev deps used as real-model fixtures for importer tests. |
| test_runner.py | Add test-only INSTALLED_APPS and pin settings (DEFAULT_AUTO_FIELD, USE_TZ) for stable CI behavior. |
| CHANGELOG.md | Document 4.2.0 features, behavior changes, and upgrade notes. |
| docs/index.md | Add operations to the docs toctree. |
| docs/operations.md | New operations guide for management commands and migrations/importers. |
| docs/enforcement.md | Document exemption semantics and new step-up enforcement/decorator usage. |
| docs/security.md | Clarify compromised-session handling and direct users to step-up guidance. |
| docs/settings.md | Add MFA_STEPUP_MAX_AGE documentation and update system checks section for E005. |
| docs/upgrading.md | Add 4.2.0 upgrade note describing step-up gating behavior change. |
| docs/api.md | Document new session freshness APIs, exemption model, and new signal semantics. |
| django_mfa/conf.py | Add MFA_STEPUP_MAX_AGE default. |
| django_mfa/checks.py | Add system check E005 validating MFA_STEPUP_MAX_AGE. |
| django_mfa/apps.py | Register the new E005 check at startup. |
| django_mfa/session.py | Add verified_at() / is_fresh() to support step-up freshness enforcement. |
| django_mfa/decorators.py | Implement mfa_recent_required / MfaRecentRequiredMixin and step-up redirect behavior. |
| django_mfa/views/enroll.py | Gate enroll_factor with step-up (allow_unenrolled=True). |
| django_mfa/views/manage.py | Gate factor mutation and recovery code generation with step-up; adjust enrollment-required computation. |
| django_mfa/views/picker.py | Preserve and forward safe ?next= through picker/single-adapter redirect paths. |
| django_mfa/views/verify.py | Factor out safe next handling (safe_next_or_none) for reuse without open-redirect risk. |
| django_mfa/templates/django_mfa/picker.html | Append next parameter to verification links when present. |
| django_mfa/middleware.py | Adjust enrollment-wall evaluation order to reduce unnecessary work and account for exemptions. |
| django_mfa/policy.py | Add exemption-aware MFA_REQUIRED evaluation (has_active_exemption, updated mfa_required_for). |
| django_mfa/models.py | Add MfaExemption model + manager with active/expiry behavior. |
| django_mfa/migrations/0009_mfa_exemption.py | Add migration creating the MfaExemption table. |
| django_mfa/admin.py | Register MfaExemption admin as inspect-and-revoke only. |
| django_mfa/events.py | Add mfa_exemption_changed signal and update request=None semantics documentation. |
| django_mfa/signals.py | Re-export mfa_exemption_changed. |
| django_mfa/management/init.py | Package marker for management modules. |
| django_mfa/management/commands/init.py | Package marker for management commands. |
| django_mfa/management/commands/_support.py | Shared helpers for commands: user resolution, date formatting, safe authenticator display. |
| django_mfa/management/commands/mfa_status.py | New read-only support command to display one user’s MFA state. |
| django_mfa/management/commands/mfa_reset.py | New support command to remove all factors and emit audit signals. |
| django_mfa/management/commands/mfa_report.py | New reporting command for rollout coverage + required-but-unenrolled (text/CSV). |
| django_mfa/management/commands/mfa_disable.py | New command to grant/revoke exemptions with reason/until validation + signal emission. |
| django_mfa/management/commands/mfa_import_django_otp.py | New importer from django-otp (+ django-two-factor-auth) with safety checks and dry-run/overwrite support. |
| django_mfa/management/commands/mfa_import_django_mfa2.py | New importer from django-mfa2 with username-resolution logic and explicit non-migratable reporting. |
| django_mfa/tests/test_stepup.py | New test suite for freshness primitives, decorator/mixin behavior, and next handling. |
| django_mfa/tests/test_views.py | Update tests for step-up gating; add helpers to stamp fresh sessions where needed. |
| django_mfa/tests/test_notifications.py | Update tests to avoid step-up gate interference; adjust verified session timestamps. |
| django_mfa/tests/test_events.py | Update verified session timestamps to satisfy step-up gating for factor changes. |
| django_mfa/tests/test_models.py | Update model registry expectation to include MfaExemption. |
| django_mfa/tests/test_migrations.py | Update migration list to include 0009. |
| django_mfa/tests/test_exemptions.py | New tests for exemption model behavior, policy/middleware effects, and mfa_disable command. |
| django_mfa/tests/test_commands.py | New tests for operational commands (status/reset/report) and invariants (no secret leakage, CSV format, etc.). |
| django_mfa/tests/test_import_django_otp.py | New importer tests against real django-otp models and edge cases. |
| django_mfa/tests/test_import_django_mfa2.py | New importer tests against real django-mfa2 models and edge cases. |
Suppressed comments (1)
django_mfa/events.py:35
factor_removed’s docstring says it can come frommfa_disable, butmfa_disabledoesn’t remove factors; it only grants/revokesMfaExemption. This could confuse audit receivers looking for operator-initiated factor removals.
#: passed by value rather than as an instance. `request` is None when a
#: management command (mfa_reset, mfa_disable) is the one removing it --
#: there is no request to pass.
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
Comment on lines
14
to
+18
| `sender` is uniformly the Adapter *class* for the factor involved, so a | ||
| receiver can narrow with `sender=TOTPAdapter`. `request` is always supplied: | ||
| every emission point sits inside a request path. | ||
| receiver can narrow with `sender=TOTPAdapter`. `request` is the request the | ||
| event happened in, or **None** when it did not happen in one: the management | ||
| commands (`mfa_reset`, `mfa_disable`) emit these too, because a factor | ||
| removed by an operator is exactly the event an audit receiver most needs to |
Comment on lines
+101
to
+103
| if (policy.resolve() | ||
| and not registry.has_primary_factor(request.user) | ||
| and policy.mfa_required_for(request.user)): |
Comment on lines
+40
to
+43
| "mfa_enrollment_required": bool( | ||
| policy.resolve() | ||
| and not registry.has_primary_factor(request.user) | ||
| and policy.mfa_required_for(request.user)), |
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.
…irements
MFA_STEPUP_MAX_AGEand its interaction withMFA_REMEMBER_MY_BROWSER.MFA_STEPUP_MAX_AGEfunctionality and its implications for factor changes.chore: upgrade version to 4.2.0 and update dependencies
django-otpanddjango-mfa2.fix: enforce recent challenge for factor changes
docs: clarify MFA_SECRET_ENCRYPTION_KEYS functionality
MFA_SECRET_ENCRYPTION_KEYSsigns TOTP secrets rather than encrypting them.