django-pfx 1.7.3.dev26__tar.gz → 1.7.3.dev33__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {django_pfx-1.7.3.dev26 → django_pfx-1.7.3.dev33}/PKG-INFO +1 -1
- {django_pfx-1.7.3.dev26 → django_pfx-1.7.3.dev33}/django_pfx.egg-info/PKG-INFO +1 -1
- {django_pfx-1.7.3.dev26 → django_pfx-1.7.3.dev33}/django_pfx.egg-info/SOURCES.txt +2 -0
- {django_pfx-1.7.3.dev26 → django_pfx-1.7.3.dev33}/django_pfx.egg-info/scm_file_list.json +2 -0
- django_pfx-1.7.3.dev33/django_pfx.egg-info/scm_version.json +8 -0
- {django_pfx-1.7.3.dev26 → django_pfx-1.7.3.dev33}/doc/source/api.views.rst +5 -0
- {django_pfx-1.7.3.dev26 → django_pfx-1.7.3.dev33}/doc/source/authentication.md +72 -9
- {django_pfx-1.7.3.dev26 → django_pfx-1.7.3.dev33}/pfx/pfxcore/exceptions.py +4 -5
- django_pfx-1.7.3.dev33/pfx/pfxcore/locale/fr/LC_MESSAGES/django.mo +0 -0
- {django_pfx-1.7.3.dev26 → django_pfx-1.7.3.dev33}/pfx/pfxcore/locale/fr/LC_MESSAGES/django.po +70 -77
- {django_pfx-1.7.3.dev26 → django_pfx-1.7.3.dev33}/pfx/pfxcore/mfa/base.py +8 -0
- {django_pfx-1.7.3.dev26 → django_pfx-1.7.3.dev33}/pfx/pfxcore/models/mfa_user_mixin.py +8 -1
- {django_pfx-1.7.3.dev26 → django_pfx-1.7.3.dev33}/pfx/pfxcore/views/__init__.py +1 -0
- {django_pfx-1.7.3.dev26 → django_pfx-1.7.3.dev33}/pfx/pfxcore/views/authentication_views.py +13 -424
- django_pfx-1.7.3.dev33/pfx/pfxcore/views/mfa_authentication_views.py +523 -0
- django_pfx-1.7.3.dev33/tests/mfa_urls.py +41 -0
- {django_pfx-1.7.3.dev26 → django_pfx-1.7.3.dev33}/tests/tests/test_auth_api.py +19 -0
- {django_pfx-1.7.3.dev26 → django_pfx-1.7.3.dev33}/tests/tests/test_mfa_login_api.py +9 -5
- {django_pfx-1.7.3.dev26 → django_pfx-1.7.3.dev33}/tests/tests/test_mfa_setup_api.py +152 -0
- django_pfx-1.7.3.dev26/django_pfx.egg-info/scm_version.json +0 -8
- django_pfx-1.7.3.dev26/pfx/pfxcore/locale/fr/LC_MESSAGES/django.mo +0 -0
- {django_pfx-1.7.3.dev26 → django_pfx-1.7.3.dev33}/.gitignore +0 -0
- {django_pfx-1.7.3.dev26 → django_pfx-1.7.3.dev33}/.gitlab-ci.yml +0 -0
- {django_pfx-1.7.3.dev26 → django_pfx-1.7.3.dev33}/.pre-commit-config.yaml +0 -0
- {django_pfx-1.7.3.dev26 → django_pfx-1.7.3.dev33}/LICENSE +0 -0
- {django_pfx-1.7.3.dev26 → django_pfx-1.7.3.dev33}/MANIFEST.in +0 -0
- {django_pfx-1.7.3.dev26 → django_pfx-1.7.3.dev33}/README.md +0 -0
- {django_pfx-1.7.3.dev26 → django_pfx-1.7.3.dev33}/django_pfx.egg-info/dependency_links.txt +0 -0
- {django_pfx-1.7.3.dev26 → django_pfx-1.7.3.dev33}/django_pfx.egg-info/requires.txt +0 -0
- {django_pfx-1.7.3.dev26 → django_pfx-1.7.3.dev33}/django_pfx.egg-info/top_level.txt +0 -0
- {django_pfx-1.7.3.dev26 → django_pfx-1.7.3.dev33}/doc/Makefile +0 -0
- {django_pfx-1.7.3.dev26 → django_pfx-1.7.3.dev33}/doc/conf.py +0 -0
- {django_pfx-1.7.3.dev26 → django_pfx-1.7.3.dev33}/doc/index.rst +0 -0
- {django_pfx-1.7.3.dev26 → django_pfx-1.7.3.dev33}/doc/source/decorator.md +0 -0
- {django_pfx-1.7.3.dev26 → django_pfx-1.7.3.dev33}/doc/source/generate_openapi.md +0 -0
- {django_pfx-1.7.3.dev26 → django_pfx-1.7.3.dev33}/doc/source/getting_started.md +0 -0
- {django_pfx-1.7.3.dev26 → django_pfx-1.7.3.dev33}/doc/source/internationalisation.md +0 -0
- {django_pfx-1.7.3.dev26 → django_pfx-1.7.3.dev33}/doc/source/model.md +0 -0
- {django_pfx-1.7.3.dev26 → django_pfx-1.7.3.dev33}/doc/source/pfx_views.md +0 -0
- {django_pfx-1.7.3.dev26 → django_pfx-1.7.3.dev33}/doc/source/profiling.md +0 -0
- {django_pfx-1.7.3.dev26 → django_pfx-1.7.3.dev33}/doc/source/settings.md +0 -0
- {django_pfx-1.7.3.dev26 → django_pfx-1.7.3.dev33}/doc/source/testing.md +0 -0
- {django_pfx-1.7.3.dev26 → django_pfx-1.7.3.dev33}/img/pfx.png +0 -0
- {django_pfx-1.7.3.dev26 → django_pfx-1.7.3.dev33}/img/pfx.svg +0 -0
- {django_pfx-1.7.3.dev26 → django_pfx-1.7.3.dev33}/make_messages +0 -0
- {django_pfx-1.7.3.dev26 → django_pfx-1.7.3.dev33}/manage.py +0 -0
- {django_pfx-1.7.3.dev26 → django_pfx-1.7.3.dev33}/pfx/__init__.py +0 -0
- {django_pfx-1.7.3.dev26 → django_pfx-1.7.3.dev33}/pfx/emails/__init__.py +0 -0
- {django_pfx-1.7.3.dev26 → django_pfx-1.7.3.dev33}/pfx/emails/apps.py +0 -0
- {django_pfx-1.7.3.dev26 → django_pfx-1.7.3.dev33}/pfx/emails/email_backend/__init__.py +0 -0
- {django_pfx-1.7.3.dev26 → django_pfx-1.7.3.dev33}/pfx/emails/email_backend/q_mailjet_email_backend.py +0 -0
- {django_pfx-1.7.3.dev26 → django_pfx-1.7.3.dev33}/pfx/emails/exceptions.py +0 -0
- {django_pfx-1.7.3.dev26 → django_pfx-1.7.3.dev33}/pfx/emails/mailjet/__init__.py +0 -0
- {django_pfx-1.7.3.dev26 → django_pfx-1.7.3.dev33}/pfx/emails/mailjet/mailjet.py +0 -0
- {django_pfx-1.7.3.dev26 → django_pfx-1.7.3.dev33}/pfx/emails/mailjet/message_result.py +0 -0
- {django_pfx-1.7.3.dev26 → django_pfx-1.7.3.dev33}/pfx/emails/migrations/0001_initial.py +0 -0
- {django_pfx-1.7.3.dev26 → django_pfx-1.7.3.dev33}/pfx/emails/migrations/0002_alter_email_headers.py +0 -0
- {django_pfx-1.7.3.dev26 → django_pfx-1.7.3.dev33}/pfx/emails/migrations/0003_alter_email_options.py +0 -0
- {django_pfx-1.7.3.dev26 → django_pfx-1.7.3.dev33}/pfx/emails/migrations/__init__.py +0 -0
- {django_pfx-1.7.3.dev26 → django_pfx-1.7.3.dev33}/pfx/emails/models/__init__.py +0 -0
- {django_pfx-1.7.3.dev26 → django_pfx-1.7.3.dev33}/pfx/emails/models/email.py +0 -0
- {django_pfx-1.7.3.dev26 → django_pfx-1.7.3.dev33}/pfx/emails/providers/__init__.py +0 -0
- {django_pfx-1.7.3.dev26 → django_pfx-1.7.3.dev33}/pfx/emails/providers/email_django_q_executor.py +0 -0
- {django_pfx-1.7.3.dev26 → django_pfx-1.7.3.dev33}/pfx/emails/shortcuts.py +0 -0
- {django_pfx-1.7.3.dev26 → django_pfx-1.7.3.dev33}/pfx/emails/tasks.py +0 -0
- {django_pfx-1.7.3.dev26 → django_pfx-1.7.3.dev33}/pfx/emails/templates/emails/base_template.html +0 -0
- {django_pfx-1.7.3.dev26 → django_pfx-1.7.3.dev33}/pfx/emails/templates/emails/base_template.txt +0 -0
- {django_pfx-1.7.3.dev26 → django_pfx-1.7.3.dev33}/pfx/emails/templates/emails/base_template_subject.txt +0 -0
- {django_pfx-1.7.3.dev26 → django_pfx-1.7.3.dev33}/pfx/emails/urls.py +0 -0
- {django_pfx-1.7.3.dev26 → django_pfx-1.7.3.dev33}/pfx/emails/views/__init__.py +0 -0
- {django_pfx-1.7.3.dev26 → django_pfx-1.7.3.dev33}/pfx/emails/views/email_rest_view.py +0 -0
- {django_pfx-1.7.3.dev26 → django_pfx-1.7.3.dev33}/pfx/pfxcore/__init__.py +0 -0
- {django_pfx-1.7.3.dev26 → django_pfx-1.7.3.dev33}/pfx/pfxcore/apidoc/__init__.py +0 -0
- {django_pfx-1.7.3.dev26 → django_pfx-1.7.3.dev33}/pfx/pfxcore/apidoc/parameters.py +0 -0
- {django_pfx-1.7.3.dev26 → django_pfx-1.7.3.dev33}/pfx/pfxcore/apidoc/schema.py +0 -0
- {django_pfx-1.7.3.dev26 → django_pfx-1.7.3.dev33}/pfx/pfxcore/apidoc/tags.py +0 -0
- {django_pfx-1.7.3.dev26 → django_pfx-1.7.3.dev33}/pfx/pfxcore/apps.py +0 -0
- {django_pfx-1.7.3.dev26 → django_pfx-1.7.3.dev33}/pfx/pfxcore/decorator/__init__.py +0 -0
- {django_pfx-1.7.3.dev26 → django_pfx-1.7.3.dev33}/pfx/pfxcore/decorator/rest.py +0 -0
- {django_pfx-1.7.3.dev26 → django_pfx-1.7.3.dev33}/pfx/pfxcore/default_settings.py +0 -0
- {django_pfx-1.7.3.dev26 → django_pfx-1.7.3.dev33}/pfx/pfxcore/fields/__init__.py +0 -0
- {django_pfx-1.7.3.dev26 → django_pfx-1.7.3.dev33}/pfx/pfxcore/fields/decimal_field.py +0 -0
- {django_pfx-1.7.3.dev26 → django_pfx-1.7.3.dev33}/pfx/pfxcore/fields/media_field.py +0 -0
- {django_pfx-1.7.3.dev26 → django_pfx-1.7.3.dev33}/pfx/pfxcore/fields/minutes_duration_field.py +0 -0
- {django_pfx-1.7.3.dev26 → django_pfx-1.7.3.dev33}/pfx/pfxcore/fields/rich_text_field.py +0 -0
- {django_pfx-1.7.3.dev26 → django_pfx-1.7.3.dev33}/pfx/pfxcore/http/__init__.py +0 -0
- {django_pfx-1.7.3.dev26 → django_pfx-1.7.3.dev33}/pfx/pfxcore/http/json_response.py +0 -0
- {django_pfx-1.7.3.dev26 → django_pfx-1.7.3.dev33}/pfx/pfxcore/management/__init__.py +0 -0
- {django_pfx-1.7.3.dev26 → django_pfx-1.7.3.dev33}/pfx/pfxcore/management/commands/__init__.py +0 -0
- {django_pfx-1.7.3.dev26 → django_pfx-1.7.3.dev33}/pfx/pfxcore/management/commands/makeapidoc.py +0 -0
- {django_pfx-1.7.3.dev26 → django_pfx-1.7.3.dev33}/pfx/pfxcore/management/commands/profile.py +0 -0
- {django_pfx-1.7.3.dev26 → django_pfx-1.7.3.dev33}/pfx/pfxcore/mfa/__init__.py +0 -0
- {django_pfx-1.7.3.dev26 → django_pfx-1.7.3.dev33}/pfx/pfxcore/mfa/authenticator.py +0 -0
- {django_pfx-1.7.3.dev26 → django_pfx-1.7.3.dev33}/pfx/pfxcore/mfa/email.py +0 -0
- {django_pfx-1.7.3.dev26 → django_pfx-1.7.3.dev33}/pfx/pfxcore/mfa/legacy.py +0 -0
- {django_pfx-1.7.3.dev26 → django_pfx-1.7.3.dev33}/pfx/pfxcore/mfa/sms.py +0 -0
- {django_pfx-1.7.3.dev26 → django_pfx-1.7.3.dev33}/pfx/pfxcore/middleware/__init__.py +0 -0
- {django_pfx-1.7.3.dev26 → django_pfx-1.7.3.dev33}/pfx/pfxcore/middleware/authentication.py +0 -0
- {django_pfx-1.7.3.dev26 → django_pfx-1.7.3.dev33}/pfx/pfxcore/middleware/locale.py +0 -0
- {django_pfx-1.7.3.dev26 → django_pfx-1.7.3.dev33}/pfx/pfxcore/middleware/profiling.py +0 -0
- {django_pfx-1.7.3.dev26 → django_pfx-1.7.3.dev33}/pfx/pfxcore/middleware/request_too_big_middleware.py +0 -0
- {django_pfx-1.7.3.dev26 → django_pfx-1.7.3.dev33}/pfx/pfxcore/middleware/ws_cookie_authentication_middleware.py +0 -0
- {django_pfx-1.7.3.dev26 → django_pfx-1.7.3.dev33}/pfx/pfxcore/migrations/0001_initial.py +0 -0
- {django_pfx-1.7.3.dev26 → django_pfx-1.7.3.dev33}/pfx/pfxcore/migrations/0002_pfxpermissionsuser.py +0 -0
- {django_pfx-1.7.3.dev26 → django_pfx-1.7.3.dev33}/pfx/pfxcore/migrations/0003_delete_pfxpermissionsuser.py +0 -0
- {django_pfx-1.7.3.dev26 → django_pfx-1.7.3.dev33}/pfx/pfxcore/migrations/0004_alter_loginban_failed_counter_and_more.py +0 -0
- {django_pfx-1.7.3.dev26 → django_pfx-1.7.3.dev33}/pfx/pfxcore/migrations/0005_mfadevice_mfadevice_mfa_device_unique_backend.py +0 -0
- {django_pfx-1.7.3.dev26 → django_pfx-1.7.3.dev33}/pfx/pfxcore/migrations/__init__.py +0 -0
- {django_pfx-1.7.3.dev26 → django_pfx-1.7.3.dev33}/pfx/pfxcore/migrations/operations/__init__.py +0 -0
- {django_pfx-1.7.3.dev26 → django_pfx-1.7.3.dev33}/pfx/pfxcore/migrations/operations/permissions.py +0 -0
- {django_pfx-1.7.3.dev26 → django_pfx-1.7.3.dev33}/pfx/pfxcore/models/__init__.py +0 -0
- {django_pfx-1.7.3.dev26 → django_pfx-1.7.3.dev33}/pfx/pfxcore/models/abstract_pfx_base_user.py +0 -0
- {django_pfx-1.7.3.dev26 → django_pfx-1.7.3.dev33}/pfx/pfxcore/models/attachment_mixin.py +0 -0
- {django_pfx-1.7.3.dev26 → django_pfx-1.7.3.dev33}/pfx/pfxcore/models/cache_mixins.py +0 -0
- {django_pfx-1.7.3.dev26 → django_pfx-1.7.3.dev33}/pfx/pfxcore/models/login_ban.py +0 -0
- {django_pfx-1.7.3.dev26 → django_pfx-1.7.3.dev33}/pfx/pfxcore/models/mfa_device.py +0 -0
- {django_pfx-1.7.3.dev26 → django_pfx-1.7.3.dev33}/pfx/pfxcore/models/not_null_fields.py +0 -0
- {django_pfx-1.7.3.dev26 → django_pfx-1.7.3.dev33}/pfx/pfxcore/models/ordered_model_mixin.py +0 -0
- {django_pfx-1.7.3.dev26 → django_pfx-1.7.3.dev33}/pfx/pfxcore/models/pfx_models.py +0 -0
- {django_pfx-1.7.3.dev26 → django_pfx-1.7.3.dev33}/pfx/pfxcore/models/pfx_user.py +0 -0
- {django_pfx-1.7.3.dev26 → django_pfx-1.7.3.dev33}/pfx/pfxcore/models/user_filtered_queryset_mixin.py +0 -0
- {django_pfx-1.7.3.dev26 → django_pfx-1.7.3.dev33}/pfx/pfxcore/serializers/__init__.py +0 -0
- {django_pfx-1.7.3.dev26 → django_pfx-1.7.3.dev33}/pfx/pfxcore/serializers/json.py +0 -0
- {django_pfx-1.7.3.dev26 → django_pfx-1.7.3.dev33}/pfx/pfxcore/settings.py +0 -0
- {django_pfx-1.7.3.dev26 → django_pfx-1.7.3.dev33}/pfx/pfxcore/shortcuts.py +0 -0
- {django_pfx-1.7.3.dev26 → django_pfx-1.7.3.dev33}/pfx/pfxcore/sms/__init__.py +0 -0
- {django_pfx-1.7.3.dev26 → django_pfx-1.7.3.dev33}/pfx/pfxcore/sms/backends/__init__.py +0 -0
- {django_pfx-1.7.3.dev26 → django_pfx-1.7.3.dev33}/pfx/pfxcore/sms/backends/base.py +0 -0
- {django_pfx-1.7.3.dev26 → django_pfx-1.7.3.dev33}/pfx/pfxcore/sms/backends/console.py +0 -0
- {django_pfx-1.7.3.dev26 → django_pfx-1.7.3.dev33}/pfx/pfxcore/storage/__init__.py +0 -0
- {django_pfx-1.7.3.dev26 → django_pfx-1.7.3.dev33}/pfx/pfxcore/storage/exceptions.py +0 -0
- {django_pfx-1.7.3.dev26 → django_pfx-1.7.3.dev33}/pfx/pfxcore/storage/local_storage.py +0 -0
- {django_pfx-1.7.3.dev26 → django_pfx-1.7.3.dev33}/pfx/pfxcore/storage/s3_storage.py +0 -0
- {django_pfx-1.7.3.dev26 → django_pfx-1.7.3.dev33}/pfx/pfxcore/templates/registration/otp_code_email.txt +0 -0
- {django_pfx-1.7.3.dev26 → django_pfx-1.7.3.dev33}/pfx/pfxcore/templates/registration/otp_code_subject.txt +0 -0
- {django_pfx-1.7.3.dev26 → django_pfx-1.7.3.dev33}/pfx/pfxcore/templates/registration/password_reset_email.txt +0 -0
- {django_pfx-1.7.3.dev26 → django_pfx-1.7.3.dev33}/pfx/pfxcore/templates/registration/password_reset_subject.txt +0 -0
- {django_pfx-1.7.3.dev26 → django_pfx-1.7.3.dev33}/pfx/pfxcore/templates/registration/welcome_email.txt +0 -0
- {django_pfx-1.7.3.dev26 → django_pfx-1.7.3.dev33}/pfx/pfxcore/templates/registration/welcome_subject.txt +0 -0
- {django_pfx-1.7.3.dev26 → django_pfx-1.7.3.dev33}/pfx/pfxcore/test.py +0 -0
- {django_pfx-1.7.3.dev26 → django_pfx-1.7.3.dev33}/pfx/pfxcore/urls.py +0 -0
- {django_pfx-1.7.3.dev26 → django_pfx-1.7.3.dev33}/pfx/pfxcore/views/fields.py +0 -0
- {django_pfx-1.7.3.dev26 → django_pfx-1.7.3.dev33}/pfx/pfxcore/views/filters_views.py +0 -0
- {django_pfx-1.7.3.dev26 → django_pfx-1.7.3.dev33}/pfx/pfxcore/views/locale_views.py +0 -0
- {django_pfx-1.7.3.dev26 → django_pfx-1.7.3.dev33}/pfx/pfxcore/views/media_rest_view_mixin.py +0 -0
- {django_pfx-1.7.3.dev26 → django_pfx-1.7.3.dev33}/pfx/pfxcore/views/ordered_rest_view_mixin.py +0 -0
- {django_pfx-1.7.3.dev26 → django_pfx-1.7.3.dev33}/pfx/pfxcore/views/parameters/__init__.py +0 -0
- {django_pfx-1.7.3.dev26 → django_pfx-1.7.3.dev33}/pfx/pfxcore/views/parameters/date_format.py +0 -0
- {django_pfx-1.7.3.dev26 → django_pfx-1.7.3.dev33}/pfx/pfxcore/views/parameters/groups.py +0 -0
- {django_pfx-1.7.3.dev26 → django_pfx-1.7.3.dev33}/pfx/pfxcore/views/parameters/list_count.py +0 -0
- {django_pfx-1.7.3.dev26 → django_pfx-1.7.3.dev33}/pfx/pfxcore/views/parameters/list_items.py +0 -0
- {django_pfx-1.7.3.dev26 → django_pfx-1.7.3.dev33}/pfx/pfxcore/views/parameters/list_mode.py +0 -0
- {django_pfx-1.7.3.dev26 → django_pfx-1.7.3.dev33}/pfx/pfxcore/views/parameters/list_order.py +0 -0
- {django_pfx-1.7.3.dev26 → django_pfx-1.7.3.dev33}/pfx/pfxcore/views/parameters/list_search.py +0 -0
- {django_pfx-1.7.3.dev26 → django_pfx-1.7.3.dev33}/pfx/pfxcore/views/parameters/media_redirect.py +0 -0
- {django_pfx-1.7.3.dev26 → django_pfx-1.7.3.dev33}/pfx/pfxcore/views/parameters/meta_fields.py +0 -0
- {django_pfx-1.7.3.dev26 → django_pfx-1.7.3.dev33}/pfx/pfxcore/views/parameters/meta_filters.py +0 -0
- {django_pfx-1.7.3.dev26 → django_pfx-1.7.3.dev33}/pfx/pfxcore/views/parameters/meta_orders.py +0 -0
- {django_pfx-1.7.3.dev26 → django_pfx-1.7.3.dev33}/pfx/pfxcore/views/parameters/subset.py +0 -0
- {django_pfx-1.7.3.dev26 → django_pfx-1.7.3.dev33}/pfx/pfxcore/views/parameters/subset_limit.py +0 -0
- {django_pfx-1.7.3.dev26 → django_pfx-1.7.3.dev33}/pfx/pfxcore/views/parameters/subset_offset.py +0 -0
- {django_pfx-1.7.3.dev26 → django_pfx-1.7.3.dev33}/pfx/pfxcore/views/parameters/subset_page.py +0 -0
- {django_pfx-1.7.3.dev26 → django_pfx-1.7.3.dev33}/pfx/pfxcore/views/parameters/subset_page_size.py +0 -0
- {django_pfx-1.7.3.dev26 → django_pfx-1.7.3.dev33}/pfx/pfxcore/views/parameters/subset_page_subset.py +0 -0
- {django_pfx-1.7.3.dev26 → django_pfx-1.7.3.dev33}/pfx/pfxcore/views/rest_views.py +0 -0
- {django_pfx-1.7.3.dev26 → django_pfx-1.7.3.dev33}/pfx/settings/__init__.py +0 -0
- {django_pfx-1.7.3.dev26 → django_pfx-1.7.3.dev33}/pfx/settings/dev.py +0 -0
- {django_pfx-1.7.3.dev26 → django_pfx-1.7.3.dev33}/pfx/userjobs/__init__.py +0 -0
- {django_pfx-1.7.3.dev26 → django_pfx-1.7.3.dev33}/pfx/userjobs/apps.py +0 -0
- {django_pfx-1.7.3.dev26 → django_pfx-1.7.3.dev33}/pfx/userjobs/locale/fr/LC_MESSAGES/django.mo +0 -0
- {django_pfx-1.7.3.dev26 → django_pfx-1.7.3.dev33}/pfx/userjobs/locale/fr/LC_MESSAGES/django.po +0 -0
- {django_pfx-1.7.3.dev26 → django_pfx-1.7.3.dev33}/pfx/userjobs/migrations/0001_initial.py +0 -0
- {django_pfx-1.7.3.dev26 → django_pfx-1.7.3.dev33}/pfx/userjobs/migrations/0002_alter_userjob_options_alter_userjob_auto_queued_and_more.py +0 -0
- {django_pfx-1.7.3.dev26 → django_pfx-1.7.3.dev33}/pfx/userjobs/migrations/0003_clean_scheduled_tasks.py +0 -0
- {django_pfx-1.7.3.dev26 → django_pfx-1.7.3.dev33}/pfx/userjobs/migrations/__init__.py +0 -0
- {django_pfx-1.7.3.dev26 → django_pfx-1.7.3.dev33}/pfx/userjobs/models/__init__.py +0 -0
- {django_pfx-1.7.3.dev26 → django_pfx-1.7.3.dev33}/pfx/userjobs/models/user_job.py +0 -0
- {django_pfx-1.7.3.dev26 → django_pfx-1.7.3.dev33}/pfx/userjobs/models/user_job_attachment.py +0 -0
- {django_pfx-1.7.3.dev26 → django_pfx-1.7.3.dev33}/pfx/userjobs/providers/__init__.py +0 -0
- {django_pfx-1.7.3.dev26 → django_pfx-1.7.3.dev33}/pfx/userjobs/providers/user_job_django_q_executor.py +0 -0
- {django_pfx-1.7.3.dev26 → django_pfx-1.7.3.dev33}/pfx/userjobs/providers/user_job_websocket_channel_provider.py +0 -0
- {django_pfx-1.7.3.dev26 → django_pfx-1.7.3.dev33}/pfx/userjobs/tasks.py +0 -0
- {django_pfx-1.7.3.dev26 → django_pfx-1.7.3.dev33}/pfx/userjobs/urls.py +0 -0
- {django_pfx-1.7.3.dev26 → django_pfx-1.7.3.dev33}/pfx/userjobs/views/__init__.py +0 -0
- {django_pfx-1.7.3.dev26 → django_pfx-1.7.3.dev33}/pfx/userjobs/views/user_job_attachment_view.py +0 -0
- {django_pfx-1.7.3.dev26 → django_pfx-1.7.3.dev33}/pfx/userjobs/views/user_job_view.py +0 -0
- {django_pfx-1.7.3.dev26 → django_pfx-1.7.3.dev33}/pfx/userjobs/views/user_websocket_consumer.py +0 -0
- {django_pfx-1.7.3.dev26 → django_pfx-1.7.3.dev33}/pyproject.toml +0 -0
- {django_pfx-1.7.3.dev26 → django_pfx-1.7.3.dev33}/requirements.txt +0 -0
- {django_pfx-1.7.3.dev26 → django_pfx-1.7.3.dev33}/serve-doc +0 -0
- {django_pfx-1.7.3.dev26 → django_pfx-1.7.3.dev33}/setup.cfg +0 -0
- {django_pfx-1.7.3.dev26 → django_pfx-1.7.3.dev33}/setup.py +0 -0
- {django_pfx-1.7.3.dev26 → django_pfx-1.7.3.dev33}/tests/__init__.py +0 -0
- {django_pfx-1.7.3.dev26 → django_pfx-1.7.3.dev33}/tests/apps.py +0 -0
- {django_pfx-1.7.3.dev26 → django_pfx-1.7.3.dev33}/tests/channel_group_send_calls.py +0 -0
- {django_pfx-1.7.3.dev26 → django_pfx-1.7.3.dev33}/tests/locale/fr/LC_MESSAGES/django.po +0 -0
- {django_pfx-1.7.3.dev26 → django_pfx-1.7.3.dev33}/tests/migrations/0001_initial.py +0 -0
- {django_pfx-1.7.3.dev26 → django_pfx-1.7.3.dev33}/tests/migrations/0002_alter_book_cover.py +0 -0
- {django_pfx-1.7.3.dev26 → django_pfx-1.7.3.dev33}/tests/migrations/0003_book_local_file.py +0 -0
- {django_pfx-1.7.3.dev26 → django_pfx-1.7.3.dev33}/tests/migrations/0004_mfausermixin_fields.py +0 -0
- {django_pfx-1.7.3.dev26 → django_pfx-1.7.3.dev33}/tests/migrations/0005_mfausermixin_fields_fix.py +0 -0
- {django_pfx-1.7.3.dev26 → django_pfx-1.7.3.dev33}/tests/migrations/0006_rename_otp_enabled_user_mfa_authenticator_enabled_and_more.py +0 -0
- {django_pfx-1.7.3.dev26 → django_pfx-1.7.3.dev33}/tests/migrations/0007_attachment.py +0 -0
- {django_pfx-1.7.3.dev26 → django_pfx-1.7.3.dev33}/tests/migrations/0008_remove_user_hotp_count_remove_user_hotp_expiry_and_more.py +0 -0
- {django_pfx-1.7.3.dev26 → django_pfx-1.7.3.dev33}/tests/migrations/__init__.py +0 -0
- {django_pfx-1.7.3.dev26 → django_pfx-1.7.3.dev33}/tests/models.py +0 -0
- {django_pfx-1.7.3.dev26 → django_pfx-1.7.3.dev33}/tests/settings/__init__.py +0 -0
- {django_pfx-1.7.3.dev26 → django_pfx-1.7.3.dev33}/tests/settings/ci.py +0 -0
- {django_pfx-1.7.3.dev26 → django_pfx-1.7.3.dev33}/tests/settings/common.py +0 -0
- {django_pfx-1.7.3.dev26 → django_pfx-1.7.3.dev33}/tests/settings/dev.py +0 -0
- {django_pfx-1.7.3.dev26 → django_pfx-1.7.3.dev33}/tests/settings/dev_custom_example.py +0 -0
- {django_pfx-1.7.3.dev26 → django_pfx-1.7.3.dev33}/tests/settings/dev_default.py +0 -0
- {django_pfx-1.7.3.dev26 → django_pfx-1.7.3.dev33}/tests/tests/__init__.py +0 -0
- {django_pfx-1.7.3.dev26 → django_pfx-1.7.3.dev33}/tests/tests/basic_api_errors.py +0 -0
- {django_pfx-1.7.3.dev26 → django_pfx-1.7.3.dev33}/tests/tests/basic_api_test.py +0 -0
- {django_pfx-1.7.3.dev26 → django_pfx-1.7.3.dev33}/tests/tests/mfa_test_utils.py +0 -0
- {django_pfx-1.7.3.dev26 → django_pfx-1.7.3.dev33}/tests/tests/test_api_doc.py +0 -0
- {django_pfx-1.7.3.dev26 → django_pfx-1.7.3.dev33}/tests/tests/test_api_doc_search.py +0 -0
- {django_pfx-1.7.3.dev26 → django_pfx-1.7.3.dev33}/tests/tests/test_body_mixin.py +0 -0
- {django_pfx-1.7.3.dev26 → django_pfx-1.7.3.dev33}/tests/tests/test_cache.py +0 -0
- {django_pfx-1.7.3.dev26 → django_pfx-1.7.3.dev33}/tests/tests/test_client.py +0 -0
- {django_pfx-1.7.3.dev26 → django_pfx-1.7.3.dev33}/tests/tests/test_emails_email_api.py +0 -0
- {django_pfx-1.7.3.dev26 → django_pfx-1.7.3.dev33}/tests/tests/test_emails_email_model.py +0 -0
- {django_pfx-1.7.3.dev26 → django_pfx-1.7.3.dev33}/tests/tests/test_emails_q_mailjet_email_backend.py +0 -0
- {django_pfx-1.7.3.dev26 → django_pfx-1.7.3.dev33}/tests/tests/test_emails_send_email_message.py +0 -0
- {django_pfx-1.7.3.dev26 → django_pfx-1.7.3.dev33}/tests/tests/test_fields_choices.py +0 -0
- {django_pfx-1.7.3.dev26 → django_pfx-1.7.3.dev33}/tests/tests/test_fields_date.py +0 -0
- {django_pfx-1.7.3.dev26 → django_pfx-1.7.3.dev33}/tests/tests/test_fields_decimal.py +0 -0
- {django_pfx-1.7.3.dev26 → django_pfx-1.7.3.dev33}/tests/tests/test_fields_minutes_duration.py +0 -0
- {django_pfx-1.7.3.dev26 → django_pfx-1.7.3.dev33}/tests/tests/test_fields_one2many.py +0 -0
- {django_pfx-1.7.3.dev26 → django_pfx-1.7.3.dev33}/tests/tests/test_fields_rich_text.py +0 -0
- {django_pfx-1.7.3.dev26 → django_pfx-1.7.3.dev33}/tests/tests/test_fields_time.py +0 -0
- {django_pfx-1.7.3.dev26 → django_pfx-1.7.3.dev33}/tests/tests/test_filters.py +0 -0
- {django_pfx-1.7.3.dev26 → django_pfx-1.7.3.dev33}/tests/tests/test_locale_api.py +0 -0
- {django_pfx-1.7.3.dev26 → django_pfx-1.7.3.dev33}/tests/tests/test_mfa_legacy.py +0 -0
- {django_pfx-1.7.3.dev26 → django_pfx-1.7.3.dev33}/tests/tests/test_ordered_rest_view_mixin.py +0 -0
- {django_pfx-1.7.3.dev26 → django_pfx-1.7.3.dev33}/tests/tests/test_perm_tests.py +0 -0
- {django_pfx-1.7.3.dev26 → django_pfx-1.7.3.dev33}/tests/tests/test_permissions.py +0 -0
- {django_pfx-1.7.3.dev26 → django_pfx-1.7.3.dev33}/tests/tests/test_perms_api.py +0 -0
- {django_pfx-1.7.3.dev26 → django_pfx-1.7.3.dev33}/tests/tests/test_post_migrate_groups_update.py +0 -0
- {django_pfx-1.7.3.dev26 → django_pfx-1.7.3.dev33}/tests/tests/test_profiling_middleware.py +0 -0
- {django_pfx-1.7.3.dev26 → django_pfx-1.7.3.dev33}/tests/tests/test_settings.py +0 -0
- {django_pfx-1.7.3.dev26 → django_pfx-1.7.3.dev33}/tests/tests/test_shortcuts.py +0 -0
- {django_pfx-1.7.3.dev26 → django_pfx-1.7.3.dev33}/tests/tests/test_timezone_middleware.py +0 -0
- {django_pfx-1.7.3.dev26 → django_pfx-1.7.3.dev33}/tests/tests/test_tools.py +0 -0
- {django_pfx-1.7.3.dev26 → django_pfx-1.7.3.dev33}/tests/tests/test_user_job_api.py +0 -0
- {django_pfx-1.7.3.dev26 → django_pfx-1.7.3.dev33}/tests/tests/test_user_job_messages.py +0 -0
- {django_pfx-1.7.3.dev26 → django_pfx-1.7.3.dev33}/tests/tests/test_user_job_model.py +0 -0
- {django_pfx-1.7.3.dev26 → django_pfx-1.7.3.dev33}/tests/tests/test_user_queryset.py +0 -0
- {django_pfx-1.7.3.dev26 → django_pfx-1.7.3.dev33}/tests/tests/test_user_websocket.py +0 -0
- {django_pfx-1.7.3.dev26 → django_pfx-1.7.3.dev33}/tests/tests/test_view_decorators.py +0 -0
- {django_pfx-1.7.3.dev26 → django_pfx-1.7.3.dev33}/tests/tests/test_view_fields.py +0 -0
- {django_pfx-1.7.3.dev26 → django_pfx-1.7.3.dev33}/tests/urls.py +0 -0
- {django_pfx-1.7.3.dev26 → django_pfx-1.7.3.dev33}/tests/views.py +0 -0
- {django_pfx-1.7.3.dev26 → django_pfx-1.7.3.dev33}/tests_base_user/__init__.py +0 -0
- {django_pfx-1.7.3.dev26 → django_pfx-1.7.3.dev33}/tests_base_user/migrations/0001_initial.py +0 -0
- {django_pfx-1.7.3.dev26 → django_pfx-1.7.3.dev33}/tests_base_user/migrations/__init__.py +0 -0
- {django_pfx-1.7.3.dev26 → django_pfx-1.7.3.dev33}/tests_base_user/settings/__init__.py +0 -0
- {django_pfx-1.7.3.dev26 → django_pfx-1.7.3.dev33}/tests_base_user/settings/ci.py +0 -0
- {django_pfx-1.7.3.dev26 → django_pfx-1.7.3.dev33}/tests_base_user/settings/common.py +0 -0
- {django_pfx-1.7.3.dev26 → django_pfx-1.7.3.dev33}/tests_base_user/settings/dev.py +0 -0
- {django_pfx-1.7.3.dev26 → django_pfx-1.7.3.dev33}/tests_base_user/settings/dev_custom_example.py +0 -0
- {django_pfx-1.7.3.dev26 → django_pfx-1.7.3.dev33}/tests_base_user/settings/dev_default.py +0 -0
- {django_pfx-1.7.3.dev26 → django_pfx-1.7.3.dev33}/tests_base_user/tests/__init__.py +0 -0
- {django_pfx-1.7.3.dev26 → django_pfx-1.7.3.dev33}/tests_base_user/tests/test_api.py +0 -0
- {django_pfx-1.7.3.dev26 → django_pfx-1.7.3.dev33}/tests_base_user/tests/test_auth_api.py +0 -0
- {django_pfx-1.7.3.dev26 → django_pfx-1.7.3.dev33}/tests_base_user/urls.py +0 -0
- {django_pfx-1.7.3.dev26 → django_pfx-1.7.3.dev33}/tests_base_user/views.py +0 -0
- {django_pfx-1.7.3.dev26 → django_pfx-1.7.3.dev33}/tests_custom_user/__init__.py +0 -0
- {django_pfx-1.7.3.dev26 → django_pfx-1.7.3.dev33}/tests_custom_user/migrations/0001_initial.py +0 -0
- {django_pfx-1.7.3.dev26 → django_pfx-1.7.3.dev33}/tests_custom_user/migrations/__init__.py +0 -0
- {django_pfx-1.7.3.dev26 → django_pfx-1.7.3.dev33}/tests_custom_user/models.py +0 -0
- {django_pfx-1.7.3.dev26 → django_pfx-1.7.3.dev33}/tests_custom_user/settings/__init__.py +0 -0
- {django_pfx-1.7.3.dev26 → django_pfx-1.7.3.dev33}/tests_custom_user/settings/ci.py +0 -0
- {django_pfx-1.7.3.dev26 → django_pfx-1.7.3.dev33}/tests_custom_user/settings/common.py +0 -0
- {django_pfx-1.7.3.dev26 → django_pfx-1.7.3.dev33}/tests_custom_user/settings/dev.py +0 -0
- {django_pfx-1.7.3.dev26 → django_pfx-1.7.3.dev33}/tests_custom_user/settings/dev_custom_example.py +0 -0
- {django_pfx-1.7.3.dev26 → django_pfx-1.7.3.dev33}/tests_custom_user/settings/dev_default.py +0 -0
- {django_pfx-1.7.3.dev26 → django_pfx-1.7.3.dev33}/tests_custom_user/tests/__init__.py +0 -0
- {django_pfx-1.7.3.dev26 → django_pfx-1.7.3.dev33}/tests_custom_user/tests/test_api.py +0 -0
- {django_pfx-1.7.3.dev26 → django_pfx-1.7.3.dev33}/tests_custom_user/tests/test_auth_api.py +0 -0
- {django_pfx-1.7.3.dev26 → django_pfx-1.7.3.dev33}/tests_custom_user/urls.py +0 -0
- {django_pfx-1.7.3.dev26 → django_pfx-1.7.3.dev33}/tests_custom_user/views.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: django-pfx
|
|
3
|
-
Version: 1.7.3.
|
|
3
|
+
Version: 1.7.3.dev33
|
|
4
4
|
Summary: Django PFX is a toolkit designed to streamline the development of RESTful APIs using the Django framework.
|
|
5
5
|
Author: Hervé Martinet
|
|
6
6
|
Author-email: herve.martinet@gmail.com
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: django-pfx
|
|
3
|
-
Version: 1.7.3.
|
|
3
|
+
Version: 1.7.3.dev33
|
|
4
4
|
Summary: Django PFX is a toolkit designed to streamline the development of RESTful APIs using the Django framework.
|
|
5
5
|
Author: Hervé Martinet
|
|
6
6
|
Author-email: herve.martinet@gmail.com
|
|
@@ -140,6 +140,7 @@ pfx/pfxcore/views/fields.py
|
|
|
140
140
|
pfx/pfxcore/views/filters_views.py
|
|
141
141
|
pfx/pfxcore/views/locale_views.py
|
|
142
142
|
pfx/pfxcore/views/media_rest_view_mixin.py
|
|
143
|
+
pfx/pfxcore/views/mfa_authentication_views.py
|
|
143
144
|
pfx/pfxcore/views/ordered_rest_view_mixin.py
|
|
144
145
|
pfx/pfxcore/views/rest_views.py
|
|
145
146
|
pfx/pfxcore/views/parameters/__init__.py
|
|
@@ -185,6 +186,7 @@ pfx/userjobs/views/user_websocket_consumer.py
|
|
|
185
186
|
tests/__init__.py
|
|
186
187
|
tests/apps.py
|
|
187
188
|
tests/channel_group_send_calls.py
|
|
189
|
+
tests/mfa_urls.py
|
|
188
190
|
tests/models.py
|
|
189
191
|
tests/urls.py
|
|
190
192
|
tests/views.py
|
|
@@ -14,6 +14,7 @@
|
|
|
14
14
|
"setup.py",
|
|
15
15
|
"setup.cfg",
|
|
16
16
|
"tests/channel_group_send_calls.py",
|
|
17
|
+
"tests/mfa_urls.py",
|
|
17
18
|
"tests/urls.py",
|
|
18
19
|
"tests/views.py",
|
|
19
20
|
"tests/__init__.py",
|
|
@@ -124,6 +125,7 @@
|
|
|
124
125
|
"pfx/pfxcore/middleware/profiling.py",
|
|
125
126
|
"pfx/pfxcore/middleware/request_too_big_middleware.py",
|
|
126
127
|
"pfx/pfxcore/views/authentication_views.py",
|
|
128
|
+
"pfx/pfxcore/views/mfa_authentication_views.py",
|
|
127
129
|
"pfx/pfxcore/views/filters_views.py",
|
|
128
130
|
"pfx/pfxcore/views/media_rest_view_mixin.py",
|
|
129
131
|
"pfx/pfxcore/views/locale_views.py",
|
|
@@ -151,6 +151,11 @@ Base services
|
|
|
151
151
|
:undoc-members:
|
|
152
152
|
:show-inheritance:
|
|
153
153
|
|
|
154
|
+
.. autoclass:: pfx.pfxcore.views.MFAAuthenticationView
|
|
155
|
+
:members:
|
|
156
|
+
:undoc-members:
|
|
157
|
+
:show-inheritance:
|
|
158
|
+
|
|
154
159
|
.. autoclass:: pfx.pfxcore.views.SignupView
|
|
155
160
|
:members:
|
|
156
161
|
:undoc-members:
|
|
@@ -126,6 +126,31 @@ class MyUser(MFAUserMixin, AbstractPFXBaseUser):
|
|
|
126
126
|
pass
|
|
127
127
|
```
|
|
128
128
|
|
|
129
|
+
Finally, the MFA endpoints (OTP login, challenge and backend management) are provided by
|
|
130
|
+
{class}`pfx.pfxcore.views.MFAAuthenticationView`, a subclass of
|
|
131
|
+
{class}`pfx.pfxcore.views.AuthenticationView`. Register it **instead of** the plain
|
|
132
|
+
`AuthenticationView` in your urls:
|
|
133
|
+
|
|
134
|
+
```python
|
|
135
|
+
from django.urls import path, include
|
|
136
|
+
from pfx.pfxcore import register_views
|
|
137
|
+
from pfx.pfxcore.views import MFAAuthenticationView
|
|
138
|
+
|
|
139
|
+
urlpatterns = [
|
|
140
|
+
path('api/', include(register_views(MFAAuthenticationView))),
|
|
141
|
+
]
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
With the default `AuthenticationView`, the `/auth/otp/login` and `/auth/mfa/*` endpoints are not
|
|
145
|
+
registered and login never returns `need_otp` — MFA is effectively disabled regardless of the user
|
|
146
|
+
mixin or settings.
|
|
147
|
+
|
|
148
|
+
```{note}
|
|
149
|
+
**Migrating from a previous version.** The MFA endpoints used to live on `AuthenticationView`; they
|
|
150
|
+
have moved to {class}`pfx.pfxcore.views.MFAAuthenticationView`. If you rely on MFA, register that
|
|
151
|
+
view (see above) in place of `AuthenticationView`.
|
|
152
|
+
```
|
|
153
|
+
|
|
129
154
|
```{note}
|
|
130
155
|
**Migrating from a previous version.** The MFA state used to live directly on the user table
|
|
131
156
|
(`otp_secret_token`, `mfa_*_enabled`, `sms_phone_number`, …) with a single shared secret. It now
|
|
@@ -147,9 +172,12 @@ removed.
|
|
|
147
172
|
{class}`pfx.pfxcore.mfa.MFABackend` subclass and add its dotted path to the list; to override a
|
|
148
173
|
built-in, list your subclass (keeping the same `id`) in place of the built-in path.
|
|
149
174
|
* `PFX_MFA_FORCE`: If `True`, MFA is required for all users — a `mfa_setup_required` flag is returned
|
|
150
|
-
at login
|
|
175
|
+
at login as long as the user has neither configured a backend nor dismissed the setup screen
|
|
176
|
+
(optional, default `False`). It is cleared as soon as any backend is confirmed (email only is
|
|
177
|
+
enough) or the user calls `PUT /auth/mfa/dismiss`.
|
|
151
178
|
A forced user who has not enrolled yet still receives an email code challenge at first login
|
|
152
|
-
(the email backend is always available).
|
|
179
|
+
(the email backend is always available). While MFA is forced, the email backend cannot be
|
|
180
|
+
disabled (nor can MFA be disabled globally); the other backends can still be disabled.
|
|
153
181
|
* `PFX_SMS_BACKEND`: Dotted path to the SMS backend class (optional, default
|
|
154
182
|
`'pfx.pfxcore.sms.backends.console.ConsoleSMSBackend'`).
|
|
155
183
|
Implement {class}`pfx.pfxcore.sms.backends.base.BaseSMSBackend` to integrate a real SMS provider.
|
|
@@ -178,7 +206,7 @@ In cookie mode, the JWT token is saved in an HTTP-only cookie.
|
|
|
178
206
|
alt Authentication success
|
|
179
207
|
API->>App: 200 OK + cookie
|
|
180
208
|
else Authentication failed
|
|
181
|
-
API->>App:
|
|
209
|
+
API->>App: 422 Unprocessable Entity
|
|
182
210
|
end
|
|
183
211
|
|
|
184
212
|
```
|
|
@@ -195,14 +223,14 @@ In cookie mode, the JWT token is saved in an HTTP-only cookie.
|
|
|
195
223
|
|
|
196
224
|
**Responses :**
|
|
197
225
|
|
|
198
|
-
* `HTTP
|
|
226
|
+
* `HTTP 422` if the credentials are incorrect (a global `__all__` error)
|
|
199
227
|
* `HTTP 200` with the following body
|
|
200
228
|
|
|
201
229
|
| Field | Description |
|
|
202
230
|
|--------------------|------------------------------------------------------------------------|
|
|
203
231
|
| token | the jwt token (only if mode is `jwt`) |
|
|
204
232
|
| user | the user object |
|
|
205
|
-
| mfa_setup_required | `true` if MFA is forced and the user has
|
|
233
|
+
| mfa_setup_required | `true` if MFA is forced and the user has neither configured a backend nor dismissed the setup screen |
|
|
206
234
|
|
|
207
235
|
|
|
208
236
|
### Login + MFA
|
|
@@ -228,10 +256,10 @@ triggers a challenge for out-of-band backends (email / SMS).
|
|
|
228
256
|
API->>App: 200 OK
|
|
229
257
|
note left of API: JWT token in cookie or in body <br/> + user in body
|
|
230
258
|
else OTP failed
|
|
231
|
-
API->>App:
|
|
259
|
+
API->>App: 422 Unprocessable Entity
|
|
232
260
|
end
|
|
233
261
|
else Login failed
|
|
234
|
-
API->>App:
|
|
262
|
+
API->>App: 422 Unprocessable Entity
|
|
235
263
|
end
|
|
236
264
|
|
|
237
265
|
```
|
|
@@ -248,7 +276,7 @@ triggers a challenge for out-of-band backends (email / SMS).
|
|
|
248
276
|
|
|
249
277
|
**Responses :**
|
|
250
278
|
|
|
251
|
-
* `HTTP
|
|
279
|
+
* `HTTP 422` if the credentials are incorrect (a global `__all__` error)
|
|
252
280
|
* `HTTP 200` with the following body when MFA is required (`need_otp=true`)
|
|
253
281
|
|
|
254
282
|
| Field | Description |
|
|
@@ -298,7 +326,7 @@ the highest-priority available backend is used.
|
|
|
298
326
|
**Responses :**
|
|
299
327
|
|
|
300
328
|
* `HTTP 401` if the temporary jwt token is incorrect
|
|
301
|
-
* `HTTP 422` if the otp code is incorrect
|
|
329
|
+
* `HTTP 422` if the otp code is incorrect (an `otp_code` field error)
|
|
302
330
|
* `HTTP 200` with the following body
|
|
303
331
|
|
|
304
332
|
| Field | Description |
|
|
@@ -318,6 +346,14 @@ The **disable** step deactivates the backend after verifying an OTP code from **
|
|
|
318
346
|
backend (e.g. you can disable SMS with an authenticator or email code). Disabling the email (base)
|
|
319
347
|
backend disables MFA entirely. A dedicated `PUT /auth/mfa/disable` disables all backends at once.
|
|
320
348
|
|
|
349
|
+
To obtain that code on an out-of-band backend (email/SMS), an **authenticated** user can request a
|
|
350
|
+
challenge with `PUT /auth/mfa/{email|sms}/challenge` — it sends a code using the same delivery
|
|
351
|
+
service as the login challenge (authenticator codes are generated locally and need no request).
|
|
352
|
+
|
|
353
|
+
When MFA is forced (`PFX_MFA_FORCE`), the email backend must stay active: disabling it — or calling
|
|
354
|
+
`PUT /auth/mfa/disable` — is rejected with `HTTP 400`. The other backends (`authenticator`, `sms`)
|
|
355
|
+
can still be disabled individually.
|
|
356
|
+
|
|
321
357
|
```{note}
|
|
322
358
|
To change an OOB target (e.g. a new phone number), disable the backend then enroll it again with
|
|
323
359
|
the new value. The email backend's target is always the user's account email and is changed through
|
|
@@ -359,6 +395,19 @@ authenticated user.
|
|
|
359
395
|
}
|
|
360
396
|
```
|
|
361
397
|
|
|
398
|
+
#### Request a challenge code (authenticated)
|
|
399
|
+
|
|
400
|
+
**Request:** `PUT` `/auth/mfa/{email|sms}/challenge`
|
|
401
|
+
|
|
402
|
+
Sends a challenge code on a **confirmed** out-of-band backend for the authenticated user, so they
|
|
403
|
+
can authorise a sensitive operation such as disabling a backend. Uses the same delivery service as
|
|
404
|
+
the login challenge. No request body.
|
|
405
|
+
|
|
406
|
+
**Responses:**
|
|
407
|
+
|
|
408
|
+
* `HTTP 403` if the backend is not out-of-band or is not enabled for the user
|
|
409
|
+
* `HTTP 200` with a confirmation message
|
|
410
|
+
|
|
362
411
|
#### Authenticator (TOTP)
|
|
363
412
|
|
|
364
413
|
**Request:** `GET` `/auth/mfa/authenticator/setup`
|
|
@@ -454,6 +503,7 @@ backends are removed too). Requires a valid OTP code from any confirmed backend.
|
|
|
454
503
|
|
|
455
504
|
**Responses:**
|
|
456
505
|
|
|
506
|
+
* `HTTP 400` if MFA is forced (email cannot be disabled)
|
|
457
507
|
* `HTTP 422` if the code is invalid
|
|
458
508
|
* `HTTP 200` with a new JWT token and updated user object
|
|
459
509
|
|
|
@@ -529,9 +579,22 @@ Disables MFA entirely (all backends). Requires a valid OTP code from any confirm
|
|
|
529
579
|
|
|
530
580
|
**Responses:**
|
|
531
581
|
|
|
582
|
+
* `HTTP 400` if MFA is forced (MFA cannot be disabled)
|
|
532
583
|
* `HTTP 422` if the code is invalid
|
|
533
584
|
* `HTTP 200` with a new JWT token and updated user object
|
|
534
585
|
|
|
586
|
+
---
|
|
587
|
+
|
|
588
|
+
**Request:** `PUT` `/auth/mfa/dismiss`
|
|
589
|
+
|
|
590
|
+
Dismisses the MFA setup screen when MFA is forced: the user states they do not want to configure
|
|
591
|
+
any backend beyond email. This clears `mfa_setup_required` without enabling an extra backend (email
|
|
592
|
+
stays the fallback). No request body and no OTP code required.
|
|
593
|
+
|
|
594
|
+
**Responses:**
|
|
595
|
+
|
|
596
|
+
* `HTTP 200` with a new JWT token and updated user object (`mfa_setup_required` is now `false`)
|
|
597
|
+
|
|
535
598
|
|
|
536
599
|
### Logout
|
|
537
600
|
A service that deletes the authentication cookie if it exists.
|
|
@@ -26,6 +26,8 @@ class APIError(Exception):
|
|
|
26
26
|
|
|
27
27
|
self.status = status
|
|
28
28
|
if validation_error:
|
|
29
|
+
# For 400 error (not validation 422 errors)
|
|
30
|
+
# allow to send debug validations errors in the api return
|
|
29
31
|
errors = []
|
|
30
32
|
errors_dict = validation_error_dict(validation_error)
|
|
31
33
|
for k, v in errors_dict.items():
|
|
@@ -77,11 +79,8 @@ class RelatedModelNotFoundAPIError(ModelValidationAPIError):
|
|
|
77
79
|
status=status, delete_cookie=delete_cookie, **kwargs)
|
|
78
80
|
|
|
79
81
|
|
|
80
|
-
class AuthenticationError(
|
|
81
|
-
|
|
82
|
-
super().__init__(
|
|
83
|
-
f(message or _("Login failed")),
|
|
84
|
-
status=status, **kwargs)
|
|
82
|
+
class AuthenticationError(ModelValidationAPIError):
|
|
83
|
+
pass
|
|
85
84
|
|
|
86
85
|
|
|
87
86
|
class UnauthorizedError(APIError):
|
|
Binary file
|
{django_pfx-1.7.3.dev26 → django_pfx-1.7.3.dev33}/pfx/pfxcore/locale/fr/LC_MESSAGES/django.po
RENAMED
|
@@ -7,7 +7,7 @@ msgid ""
|
|
|
7
7
|
msgstr ""
|
|
8
8
|
"Project-Id-Version: \n"
|
|
9
9
|
"Report-Msgid-Bugs-To: \n"
|
|
10
|
-
"POT-Creation-Date: 2026-
|
|
10
|
+
"POT-Creation-Date: 2026-07-08 16:13+0200\n"
|
|
11
11
|
"PO-Revision-Date: 2021-06-22 23:31+0200\n"
|
|
12
12
|
"Last-Translator: \n"
|
|
13
13
|
"Language-Team: \n"
|
|
@@ -28,28 +28,24 @@ msgstr ""
|
|
|
28
28
|
"Une erreur système s’est produite. Notre équipe d’assistance en a été "
|
|
29
29
|
"informée."
|
|
30
30
|
|
|
31
|
-
#: exceptions.py:
|
|
31
|
+
#: exceptions.py:55 exceptions.py:78
|
|
32
32
|
#, python-brace-format
|
|
33
33
|
msgid "{model} not found."
|
|
34
34
|
msgstr "{model} non trouvé."
|
|
35
35
|
|
|
36
|
-
#: exceptions.py:
|
|
36
|
+
#: exceptions.py:62
|
|
37
37
|
msgid "JSON Malformed {}"
|
|
38
38
|
msgstr "JSON malformé {}"
|
|
39
39
|
|
|
40
|
-
#: exceptions.py:
|
|
41
|
-
msgid "Login failed"
|
|
42
|
-
msgstr "La connexion a échoué"
|
|
43
|
-
|
|
44
|
-
#: exceptions.py:90
|
|
40
|
+
#: exceptions.py:89
|
|
45
41
|
msgid "Unauthorized"
|
|
46
42
|
msgstr "Non autorisé"
|
|
47
43
|
|
|
48
|
-
#: exceptions.py:
|
|
44
|
+
#: exceptions.py:96
|
|
49
45
|
msgid "Forbidden"
|
|
50
46
|
msgstr "Interdit"
|
|
51
47
|
|
|
52
|
-
#: exceptions.py:
|
|
48
|
+
#: exceptions.py:103
|
|
53
49
|
msgid "Resource not found"
|
|
54
50
|
msgstr "Ressource non trouvée"
|
|
55
51
|
|
|
@@ -103,22 +99,20 @@ msgid "Login bans"
|
|
|
103
99
|
msgstr "Login bannis"
|
|
104
100
|
|
|
105
101
|
#: models/mfa_device.py:20
|
|
106
|
-
#, fuzzy
|
|
107
|
-
#| msgid "Username"
|
|
108
102
|
msgid "User"
|
|
109
|
-
msgstr "
|
|
103
|
+
msgstr "Utilisateur"
|
|
110
104
|
|
|
111
105
|
#: models/mfa_device.py:22
|
|
112
106
|
msgid "Backend"
|
|
113
|
-
msgstr ""
|
|
107
|
+
msgstr "Backend"
|
|
114
108
|
|
|
115
109
|
#: models/mfa_device.py:24
|
|
116
110
|
msgid "Secret"
|
|
117
|
-
msgstr ""
|
|
111
|
+
msgstr "Secret"
|
|
118
112
|
|
|
119
113
|
#: models/mfa_device.py:26
|
|
120
114
|
msgid "Confirmed"
|
|
121
|
-
msgstr ""
|
|
115
|
+
msgstr "Confirmé"
|
|
122
116
|
|
|
123
117
|
#: models/mfa_device.py:28
|
|
124
118
|
msgid "HOTP count"
|
|
@@ -130,21 +124,21 @@ msgstr "Expiration HOTP"
|
|
|
130
124
|
|
|
131
125
|
#: models/mfa_device.py:34
|
|
132
126
|
msgid "Target"
|
|
133
|
-
msgstr ""
|
|
127
|
+
msgstr "Cible"
|
|
134
128
|
|
|
135
129
|
#: models/mfa_device.py:37
|
|
136
130
|
msgid "MFA device"
|
|
137
|
-
msgstr ""
|
|
131
|
+
msgstr "Appareil MFA"
|
|
138
132
|
|
|
139
133
|
#: models/mfa_device.py:38
|
|
140
134
|
msgid "MFA devices"
|
|
141
|
-
msgstr ""
|
|
135
|
+
msgstr "Appareils MFA"
|
|
142
136
|
|
|
143
137
|
#: models/mfa_user_mixin.py:20
|
|
144
138
|
msgid "MFA signature key"
|
|
145
|
-
msgstr ""
|
|
139
|
+
msgstr "Clé de signature MFA"
|
|
146
140
|
|
|
147
|
-
#: models/mfa_user_mixin.py:23
|
|
141
|
+
#: models/mfa_user_mixin.py:23 views/mfa_authentication_views.py:523
|
|
148
142
|
msgid "MFA setup dismissed"
|
|
149
143
|
msgstr "Configuration MFA annulée"
|
|
150
144
|
|
|
@@ -172,7 +166,7 @@ msgstr "Numéro de téléphone (SMS)"
|
|
|
172
166
|
msgid "MFA setup required"
|
|
173
167
|
msgstr "Configuration MFA requise"
|
|
174
168
|
|
|
175
|
-
#: models/mfa_user_mixin.py:
|
|
169
|
+
#: models/mfa_user_mixin.py:82
|
|
176
170
|
msgid "Is MFA forced"
|
|
177
171
|
msgstr "MFA est forcé"
|
|
178
172
|
|
|
@@ -282,7 +276,11 @@ msgstr "Bienvenue sur %(site_name)s."
|
|
|
282
276
|
msgid "Welcome on %(site_name)s"
|
|
283
277
|
msgstr "Bienvenue sur %(site_name)s"
|
|
284
278
|
|
|
285
|
-
#: views/authentication_views.py:
|
|
279
|
+
#: views/authentication_views.py:80
|
|
280
|
+
msgid "Login failed"
|
|
281
|
+
msgstr "La connexion a échoué"
|
|
282
|
+
|
|
283
|
+
#: views/authentication_views.py:89
|
|
286
284
|
#, python-brace-format
|
|
287
285
|
msgid ""
|
|
288
286
|
"Your connection is temporarily disabled after several unsuccessful attempts, "
|
|
@@ -291,78 +289,31 @@ msgstr ""
|
|
|
291
289
|
"Votre connexion est temporairement désactivée après plusieurs tentatives "
|
|
292
290
|
"infructueuses, veuillez réessayer dans {seconds} secondes."
|
|
293
291
|
|
|
294
|
-
#: views/authentication_views.py:
|
|
292
|
+
#: views/authentication_views.py:181
|
|
295
293
|
msgid "Successful login"
|
|
296
294
|
msgstr "Connexion réussie"
|
|
297
295
|
|
|
298
|
-
#: views/authentication_views.py:
|
|
299
|
-
msgid "This field is required."
|
|
300
|
-
msgstr "Ce champ est requis."
|
|
301
|
-
|
|
302
|
-
#: views/authentication_views.py:299 views/authentication_views.py:419
|
|
303
|
-
msgid "Code sent."
|
|
304
|
-
msgstr "Code envoyé."
|
|
305
|
-
|
|
306
|
-
#: views/authentication_views.py:402
|
|
307
|
-
#, fuzzy
|
|
308
|
-
#| msgid "MFA enabled"
|
|
309
|
-
msgid "MFA must be enabled first"
|
|
310
|
-
msgstr "MFA activé"
|
|
311
|
-
|
|
312
|
-
#: views/authentication_views.py:405
|
|
313
|
-
#, fuzzy
|
|
314
|
-
#| msgid "Email MFA is already enabled"
|
|
315
|
-
msgid "This MFA backend is already enabled"
|
|
316
|
-
msgstr "MFA par email déjà activé."
|
|
317
|
-
|
|
318
|
-
#: views/authentication_views.py:416
|
|
319
|
-
msgid "Phone number is required."
|
|
320
|
-
msgstr "Le numéro de téléphone est requis."
|
|
321
|
-
|
|
322
|
-
#: views/authentication_views.py:456
|
|
323
|
-
#, fuzzy
|
|
324
|
-
#| msgid "MFA enabled"
|
|
325
|
-
msgid "MFA backend enabled"
|
|
326
|
-
msgstr "MFA activé"
|
|
327
|
-
|
|
328
|
-
#: views/authentication_views.py:458 views/authentication_views.py:493
|
|
329
|
-
#: views/authentication_views.py:531
|
|
330
|
-
msgid "Invalid code"
|
|
331
|
-
msgstr "Code invalide"
|
|
332
|
-
|
|
333
|
-
#: views/authentication_views.py:497
|
|
334
|
-
#, fuzzy
|
|
335
|
-
#| msgid "MFA enabled"
|
|
336
|
-
msgid "MFA backend disabled"
|
|
337
|
-
msgstr "MFA activé"
|
|
338
|
-
|
|
339
|
-
#: views/authentication_views.py:535
|
|
340
|
-
#, fuzzy
|
|
341
|
-
#| msgid "SMS MFA disabled"
|
|
342
|
-
msgid "MFA disabled"
|
|
343
|
-
msgstr "MFA par SMS désactivé"
|
|
344
|
-
|
|
345
|
-
#: views/authentication_views.py:598 views/authentication_views.py:766
|
|
296
|
+
#: views/authentication_views.py:255 views/authentication_views.py:423
|
|
346
297
|
msgid "password updated successfully"
|
|
347
298
|
msgstr "le mot de passe a été mis à jour avec succès"
|
|
348
299
|
|
|
349
|
-
#: views/authentication_views.py:
|
|
300
|
+
#: views/authentication_views.py:260
|
|
350
301
|
msgid "Incorrect password"
|
|
351
302
|
msgstr "Mot de passe incorrect"
|
|
352
303
|
|
|
353
|
-
#: views/authentication_views.py:
|
|
304
|
+
#: views/authentication_views.py:263 views/authentication_views.py:432
|
|
354
305
|
msgid "Empty password is not allowed"
|
|
355
306
|
msgstr "Un mot de passe vide n’est pas autorisé"
|
|
356
307
|
|
|
357
|
-
#: views/authentication_views.py:
|
|
308
|
+
#: views/authentication_views.py:352
|
|
358
309
|
msgid "User and token are valid"
|
|
359
310
|
msgstr "L’utilisateur et le token sont valides"
|
|
360
311
|
|
|
361
|
-
#: views/authentication_views.py:
|
|
312
|
+
#: views/authentication_views.py:354
|
|
362
313
|
msgid "User or token is invalid"
|
|
363
314
|
msgstr "L’utilisateur ou le token est invalide"
|
|
364
315
|
|
|
365
|
-
#: views/authentication_views.py:
|
|
316
|
+
#: views/authentication_views.py:631
|
|
366
317
|
msgid ""
|
|
367
318
|
"If the email address you entered is correct, you will receive an email from "
|
|
368
319
|
"us with instructions to reset your password."
|
|
@@ -380,6 +331,48 @@ msgstr "Valeur invalide pour le filtre {filter}"
|
|
|
380
331
|
msgid "Unexpected storage error"
|
|
381
332
|
msgstr "Erreur de stockage inattendue"
|
|
382
333
|
|
|
334
|
+
#: views/mfa_authentication_views.py:161 views/mfa_authentication_views.py:407
|
|
335
|
+
#: views/mfa_authentication_views.py:446 views/mfa_authentication_views.py:488
|
|
336
|
+
msgid "Invalid code"
|
|
337
|
+
msgstr "Code invalide"
|
|
338
|
+
|
|
339
|
+
#: views/mfa_authentication_views.py:208
|
|
340
|
+
msgid "This field is required."
|
|
341
|
+
msgstr "Ce champ est requis."
|
|
342
|
+
|
|
343
|
+
#: views/mfa_authentication_views.py:214 views/mfa_authentication_views.py:288
|
|
344
|
+
#: views/mfa_authentication_views.py:368
|
|
345
|
+
msgid "Code sent."
|
|
346
|
+
msgstr "Code envoyé."
|
|
347
|
+
|
|
348
|
+
#: views/mfa_authentication_views.py:351
|
|
349
|
+
msgid "MFA must be enabled first"
|
|
350
|
+
msgstr "Le MFA doit d’abord être activé"
|
|
351
|
+
|
|
352
|
+
#: views/mfa_authentication_views.py:354
|
|
353
|
+
msgid "This MFA backend is already enabled"
|
|
354
|
+
msgstr "Ce backend MFA est déjà activé"
|
|
355
|
+
|
|
356
|
+
#: views/mfa_authentication_views.py:365
|
|
357
|
+
msgid "Phone number is required."
|
|
358
|
+
msgstr "Le numéro de téléphone est requis."
|
|
359
|
+
|
|
360
|
+
#: views/mfa_authentication_views.py:405
|
|
361
|
+
msgid "MFA backend enabled"
|
|
362
|
+
msgstr "Backend MFA activé"
|
|
363
|
+
|
|
364
|
+
#: views/mfa_authentication_views.py:441 views/mfa_authentication_views.py:483
|
|
365
|
+
msgid "MFA is required and cannot be disabled"
|
|
366
|
+
msgstr "Le MFA est requis et ne peut pas être désactivé"
|
|
367
|
+
|
|
368
|
+
#: views/mfa_authentication_views.py:450
|
|
369
|
+
msgid "MFA backend disabled"
|
|
370
|
+
msgstr "Backend MFA désactivé"
|
|
371
|
+
|
|
372
|
+
#: views/mfa_authentication_views.py:492
|
|
373
|
+
msgid "MFA disabled"
|
|
374
|
+
msgstr "MFA désactivé"
|
|
375
|
+
|
|
383
376
|
#: views/ordered_rest_view_mixin.py:32
|
|
384
377
|
#, python-brace-format
|
|
385
378
|
msgid "object parameter is mandatory for move={move}."
|
|
@@ -52,6 +52,14 @@ class MFABackend:
|
|
|
52
52
|
"""Confirm the activation with a code. Returns ``True`` on success."""
|
|
53
53
|
raise NotImplementedError
|
|
54
54
|
|
|
55
|
+
def can_disable(self, user):
|
|
56
|
+
"""Return ``True`` if the user is allowed to disable this backend.
|
|
57
|
+
|
|
58
|
+
The base backend (email) cannot be disabled while MFA is forced: it
|
|
59
|
+
must stay available so a forced user can always receive a login code.
|
|
60
|
+
Other backends can always be disabled."""
|
|
61
|
+
return not (self.is_base and user.mfa_forced)
|
|
62
|
+
|
|
55
63
|
def disable(self, user):
|
|
56
64
|
"""Disable this backend by removing its device."""
|
|
57
65
|
device = self.get_device(user)
|
|
@@ -70,7 +70,14 @@ class MFAUserMixin(models.Model):
|
|
|
70
70
|
|
|
71
71
|
@rest_property(_("MFA setup required"), "BooleanField")
|
|
72
72
|
def mfa_setup_required(self):
|
|
73
|
-
|
|
73
|
+
"""Return True if the forced user must still choose a MFA setup.
|
|
74
|
+
|
|
75
|
+
Cleared once the user either configured a backend (any confirmed
|
|
76
|
+
device, e.g. email only) or explicitly dismissed the setup screen."""
|
|
77
|
+
return (
|
|
78
|
+
self.mfa_forced
|
|
79
|
+
and not self.mfa_setup_dismissed
|
|
80
|
+
and not self.mfa_devices.filter(confirmed=True).exists())
|
|
74
81
|
|
|
75
82
|
@rest_property(_("Is MFA forced"), "BooleanField")
|
|
76
83
|
def mfa_forced(self):
|
|
@@ -12,6 +12,7 @@ from .media_rest_view_mixin import (
|
|
|
12
12
|
MediaRestViewMixin,
|
|
13
13
|
get_media_field_response,
|
|
14
14
|
)
|
|
15
|
+
from .mfa_authentication_views import MFAAuthenticationView
|
|
15
16
|
from .ordered_rest_view_mixin import OrderedRestViewMixin
|
|
16
17
|
from .rest_views import (
|
|
17
18
|
BaseRestView,
|