django-pfx 1.7.3.dev12__tar.gz → 1.7.3.dev16__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.dev12 → django_pfx-1.7.3.dev16}/PKG-INFO +1 -1
- {django_pfx-1.7.3.dev12 → django_pfx-1.7.3.dev16}/django_pfx.egg-info/PKG-INFO +1 -1
- {django_pfx-1.7.3.dev12 → django_pfx-1.7.3.dev16}/django_pfx.egg-info/SOURCES.txt +2 -0
- {django_pfx-1.7.3.dev12 → django_pfx-1.7.3.dev16}/doc/source/authentication.md +177 -33
- django_pfx-1.7.3.dev16/pfx/pfxcore/locale/fr/LC_MESSAGES/django.mo +0 -0
- {django_pfx-1.7.3.dev12 → django_pfx-1.7.3.dev16}/pfx/pfxcore/locale/fr/LC_MESSAGES/django.po +54 -29
- {django_pfx-1.7.3.dev12 → django_pfx-1.7.3.dev16}/pfx/pfxcore/middleware/__init__.py +3 -0
- django_pfx-1.7.3.dev16/pfx/pfxcore/middleware/ws_cookie_authentication_middleware.py +45 -0
- {django_pfx-1.7.3.dev12 → django_pfx-1.7.3.dev16}/pfx/pfxcore/models/mfa_user_mixin.py +119 -14
- {django_pfx-1.7.3.dev12 → django_pfx-1.7.3.dev16}/pfx/pfxcore/views/authentication_views.py +339 -39
- {django_pfx-1.7.3.dev12 → django_pfx-1.7.3.dev16}/requirements.txt +1 -1
- {django_pfx-1.7.3.dev12 → django_pfx-1.7.3.dev16}/tests/tests/__init__.py +1 -0
- {django_pfx-1.7.3.dev12 → django_pfx-1.7.3.dev16}/tests/tests/test_auth_api.py +488 -118
- django_pfx-1.7.3.dev16/tests/tests/test_user_websocket.py +79 -0
- django_pfx-1.7.3.dev12/pfx/pfxcore/locale/fr/LC_MESSAGES/django.mo +0 -0
- {django_pfx-1.7.3.dev12 → django_pfx-1.7.3.dev16}/.gitignore +0 -0
- {django_pfx-1.7.3.dev12 → django_pfx-1.7.3.dev16}/.gitlab-ci.yml +0 -0
- {django_pfx-1.7.3.dev12 → django_pfx-1.7.3.dev16}/.pre-commit-config.yaml +0 -0
- {django_pfx-1.7.3.dev12 → django_pfx-1.7.3.dev16}/LICENSE +0 -0
- {django_pfx-1.7.3.dev12 → django_pfx-1.7.3.dev16}/MANIFEST.in +0 -0
- {django_pfx-1.7.3.dev12 → django_pfx-1.7.3.dev16}/README.md +0 -0
- {django_pfx-1.7.3.dev12 → django_pfx-1.7.3.dev16}/django_pfx.egg-info/dependency_links.txt +0 -0
- {django_pfx-1.7.3.dev12 → django_pfx-1.7.3.dev16}/django_pfx.egg-info/requires.txt +0 -0
- {django_pfx-1.7.3.dev12 → django_pfx-1.7.3.dev16}/django_pfx.egg-info/top_level.txt +0 -0
- {django_pfx-1.7.3.dev12 → django_pfx-1.7.3.dev16}/doc/Makefile +0 -0
- {django_pfx-1.7.3.dev12 → django_pfx-1.7.3.dev16}/doc/conf.py +0 -0
- {django_pfx-1.7.3.dev12 → django_pfx-1.7.3.dev16}/doc/index.rst +0 -0
- {django_pfx-1.7.3.dev12 → django_pfx-1.7.3.dev16}/doc/source/api.views.rst +0 -0
- {django_pfx-1.7.3.dev12 → django_pfx-1.7.3.dev16}/doc/source/decorator.md +0 -0
- {django_pfx-1.7.3.dev12 → django_pfx-1.7.3.dev16}/doc/source/generate_openapi.md +0 -0
- {django_pfx-1.7.3.dev12 → django_pfx-1.7.3.dev16}/doc/source/getting_started.md +0 -0
- {django_pfx-1.7.3.dev12 → django_pfx-1.7.3.dev16}/doc/source/internationalisation.md +0 -0
- {django_pfx-1.7.3.dev12 → django_pfx-1.7.3.dev16}/doc/source/model.md +0 -0
- {django_pfx-1.7.3.dev12 → django_pfx-1.7.3.dev16}/doc/source/pfx_views.md +0 -0
- {django_pfx-1.7.3.dev12 → django_pfx-1.7.3.dev16}/doc/source/profiling.md +0 -0
- {django_pfx-1.7.3.dev12 → django_pfx-1.7.3.dev16}/doc/source/settings.md +0 -0
- {django_pfx-1.7.3.dev12 → django_pfx-1.7.3.dev16}/doc/source/testing.md +0 -0
- {django_pfx-1.7.3.dev12 → django_pfx-1.7.3.dev16}/img/pfx.png +0 -0
- {django_pfx-1.7.3.dev12 → django_pfx-1.7.3.dev16}/img/pfx.svg +0 -0
- {django_pfx-1.7.3.dev12 → django_pfx-1.7.3.dev16}/make_messages +0 -0
- {django_pfx-1.7.3.dev12 → django_pfx-1.7.3.dev16}/manage.py +0 -0
- {django_pfx-1.7.3.dev12 → django_pfx-1.7.3.dev16}/pfx/__init__.py +0 -0
- {django_pfx-1.7.3.dev12 → django_pfx-1.7.3.dev16}/pfx/emails/__init__.py +0 -0
- {django_pfx-1.7.3.dev12 → django_pfx-1.7.3.dev16}/pfx/emails/apps.py +0 -0
- {django_pfx-1.7.3.dev12 → django_pfx-1.7.3.dev16}/pfx/emails/email_backend/__init__.py +0 -0
- {django_pfx-1.7.3.dev12 → django_pfx-1.7.3.dev16}/pfx/emails/email_backend/q_mailjet_email_backend.py +0 -0
- {django_pfx-1.7.3.dev12 → django_pfx-1.7.3.dev16}/pfx/emails/exceptions.py +0 -0
- {django_pfx-1.7.3.dev12 → django_pfx-1.7.3.dev16}/pfx/emails/mailjet/__init__.py +0 -0
- {django_pfx-1.7.3.dev12 → django_pfx-1.7.3.dev16}/pfx/emails/mailjet/mailjet.py +0 -0
- {django_pfx-1.7.3.dev12 → django_pfx-1.7.3.dev16}/pfx/emails/mailjet/message_result.py +0 -0
- {django_pfx-1.7.3.dev12 → django_pfx-1.7.3.dev16}/pfx/emails/migrations/0001_initial.py +0 -0
- {django_pfx-1.7.3.dev12 → django_pfx-1.7.3.dev16}/pfx/emails/migrations/0002_alter_email_headers.py +0 -0
- {django_pfx-1.7.3.dev12 → django_pfx-1.7.3.dev16}/pfx/emails/migrations/0003_alter_email_options.py +0 -0
- {django_pfx-1.7.3.dev12 → django_pfx-1.7.3.dev16}/pfx/emails/migrations/__init__.py +0 -0
- {django_pfx-1.7.3.dev12 → django_pfx-1.7.3.dev16}/pfx/emails/models/__init__.py +0 -0
- {django_pfx-1.7.3.dev12 → django_pfx-1.7.3.dev16}/pfx/emails/models/email.py +0 -0
- {django_pfx-1.7.3.dev12 → django_pfx-1.7.3.dev16}/pfx/emails/providers/__init__.py +0 -0
- {django_pfx-1.7.3.dev12 → django_pfx-1.7.3.dev16}/pfx/emails/providers/email_django_q_executor.py +0 -0
- {django_pfx-1.7.3.dev12 → django_pfx-1.7.3.dev16}/pfx/emails/shortcuts.py +0 -0
- {django_pfx-1.7.3.dev12 → django_pfx-1.7.3.dev16}/pfx/emails/tasks.py +0 -0
- {django_pfx-1.7.3.dev12 → django_pfx-1.7.3.dev16}/pfx/emails/templates/emails/base_template.html +0 -0
- {django_pfx-1.7.3.dev12 → django_pfx-1.7.3.dev16}/pfx/emails/templates/emails/base_template.txt +0 -0
- {django_pfx-1.7.3.dev12 → django_pfx-1.7.3.dev16}/pfx/emails/templates/emails/base_template_subject.txt +0 -0
- {django_pfx-1.7.3.dev12 → django_pfx-1.7.3.dev16}/pfx/emails/urls.py +0 -0
- {django_pfx-1.7.3.dev12 → django_pfx-1.7.3.dev16}/pfx/emails/views/__init__.py +0 -0
- {django_pfx-1.7.3.dev12 → django_pfx-1.7.3.dev16}/pfx/emails/views/email_rest_view.py +0 -0
- {django_pfx-1.7.3.dev12 → django_pfx-1.7.3.dev16}/pfx/pfxcore/__init__.py +0 -0
- {django_pfx-1.7.3.dev12 → django_pfx-1.7.3.dev16}/pfx/pfxcore/apidoc/__init__.py +0 -0
- {django_pfx-1.7.3.dev12 → django_pfx-1.7.3.dev16}/pfx/pfxcore/apidoc/parameters.py +0 -0
- {django_pfx-1.7.3.dev12 → django_pfx-1.7.3.dev16}/pfx/pfxcore/apidoc/schema.py +0 -0
- {django_pfx-1.7.3.dev12 → django_pfx-1.7.3.dev16}/pfx/pfxcore/apidoc/tags.py +0 -0
- {django_pfx-1.7.3.dev12 → django_pfx-1.7.3.dev16}/pfx/pfxcore/apps.py +0 -0
- {django_pfx-1.7.3.dev12 → django_pfx-1.7.3.dev16}/pfx/pfxcore/decorator/__init__.py +0 -0
- {django_pfx-1.7.3.dev12 → django_pfx-1.7.3.dev16}/pfx/pfxcore/decorator/rest.py +0 -0
- {django_pfx-1.7.3.dev12 → django_pfx-1.7.3.dev16}/pfx/pfxcore/default_settings.py +0 -0
- {django_pfx-1.7.3.dev12 → django_pfx-1.7.3.dev16}/pfx/pfxcore/exceptions.py +0 -0
- {django_pfx-1.7.3.dev12 → django_pfx-1.7.3.dev16}/pfx/pfxcore/fields/__init__.py +0 -0
- {django_pfx-1.7.3.dev12 → django_pfx-1.7.3.dev16}/pfx/pfxcore/fields/decimal_field.py +0 -0
- {django_pfx-1.7.3.dev12 → django_pfx-1.7.3.dev16}/pfx/pfxcore/fields/media_field.py +0 -0
- {django_pfx-1.7.3.dev12 → django_pfx-1.7.3.dev16}/pfx/pfxcore/fields/minutes_duration_field.py +0 -0
- {django_pfx-1.7.3.dev12 → django_pfx-1.7.3.dev16}/pfx/pfxcore/fields/rich_text_field.py +0 -0
- {django_pfx-1.7.3.dev12 → django_pfx-1.7.3.dev16}/pfx/pfxcore/http/__init__.py +0 -0
- {django_pfx-1.7.3.dev12 → django_pfx-1.7.3.dev16}/pfx/pfxcore/http/json_response.py +0 -0
- {django_pfx-1.7.3.dev12 → django_pfx-1.7.3.dev16}/pfx/pfxcore/management/__init__.py +0 -0
- {django_pfx-1.7.3.dev12 → django_pfx-1.7.3.dev16}/pfx/pfxcore/management/commands/__init__.py +0 -0
- {django_pfx-1.7.3.dev12 → django_pfx-1.7.3.dev16}/pfx/pfxcore/management/commands/makeapidoc.py +0 -0
- {django_pfx-1.7.3.dev12 → django_pfx-1.7.3.dev16}/pfx/pfxcore/management/commands/profile.py +0 -0
- {django_pfx-1.7.3.dev12 → django_pfx-1.7.3.dev16}/pfx/pfxcore/middleware/authentication.py +0 -0
- {django_pfx-1.7.3.dev12 → django_pfx-1.7.3.dev16}/pfx/pfxcore/middleware/locale.py +0 -0
- {django_pfx-1.7.3.dev12 → django_pfx-1.7.3.dev16}/pfx/pfxcore/middleware/profiling.py +0 -0
- {django_pfx-1.7.3.dev12 → django_pfx-1.7.3.dev16}/pfx/pfxcore/middleware/request_too_big_middleware.py +0 -0
- {django_pfx-1.7.3.dev12 → django_pfx-1.7.3.dev16}/pfx/pfxcore/migrations/0001_initial.py +0 -0
- {django_pfx-1.7.3.dev12 → django_pfx-1.7.3.dev16}/pfx/pfxcore/migrations/0002_pfxpermissionsuser.py +0 -0
- {django_pfx-1.7.3.dev12 → django_pfx-1.7.3.dev16}/pfx/pfxcore/migrations/0003_delete_pfxpermissionsuser.py +0 -0
- {django_pfx-1.7.3.dev12 → django_pfx-1.7.3.dev16}/pfx/pfxcore/migrations/0004_alter_loginban_failed_counter_and_more.py +0 -0
- {django_pfx-1.7.3.dev12 → django_pfx-1.7.3.dev16}/pfx/pfxcore/migrations/__init__.py +0 -0
- {django_pfx-1.7.3.dev12 → django_pfx-1.7.3.dev16}/pfx/pfxcore/migrations/operations/__init__.py +0 -0
- {django_pfx-1.7.3.dev12 → django_pfx-1.7.3.dev16}/pfx/pfxcore/migrations/operations/permissions.py +0 -0
- {django_pfx-1.7.3.dev12 → django_pfx-1.7.3.dev16}/pfx/pfxcore/models/__init__.py +0 -0
- {django_pfx-1.7.3.dev12 → django_pfx-1.7.3.dev16}/pfx/pfxcore/models/abstract_pfx_base_user.py +0 -0
- {django_pfx-1.7.3.dev12 → django_pfx-1.7.3.dev16}/pfx/pfxcore/models/attachment_mixin.py +0 -0
- {django_pfx-1.7.3.dev12 → django_pfx-1.7.3.dev16}/pfx/pfxcore/models/cache_mixins.py +0 -0
- {django_pfx-1.7.3.dev12 → django_pfx-1.7.3.dev16}/pfx/pfxcore/models/login_ban.py +0 -0
- {django_pfx-1.7.3.dev12 → django_pfx-1.7.3.dev16}/pfx/pfxcore/models/not_null_fields.py +0 -0
- {django_pfx-1.7.3.dev12 → django_pfx-1.7.3.dev16}/pfx/pfxcore/models/ordered_model_mixin.py +0 -0
- {django_pfx-1.7.3.dev12 → django_pfx-1.7.3.dev16}/pfx/pfxcore/models/pfx_models.py +0 -0
- {django_pfx-1.7.3.dev12 → django_pfx-1.7.3.dev16}/pfx/pfxcore/models/pfx_user.py +0 -0
- {django_pfx-1.7.3.dev12 → django_pfx-1.7.3.dev16}/pfx/pfxcore/models/user_filtered_queryset_mixin.py +0 -0
- {django_pfx-1.7.3.dev12 → django_pfx-1.7.3.dev16}/pfx/pfxcore/serializers/__init__.py +0 -0
- {django_pfx-1.7.3.dev12 → django_pfx-1.7.3.dev16}/pfx/pfxcore/serializers/json.py +0 -0
- {django_pfx-1.7.3.dev12 → django_pfx-1.7.3.dev16}/pfx/pfxcore/settings.py +0 -0
- {django_pfx-1.7.3.dev12 → django_pfx-1.7.3.dev16}/pfx/pfxcore/shortcuts.py +0 -0
- {django_pfx-1.7.3.dev12 → django_pfx-1.7.3.dev16}/pfx/pfxcore/sms/__init__.py +0 -0
- {django_pfx-1.7.3.dev12 → django_pfx-1.7.3.dev16}/pfx/pfxcore/sms/backends/__init__.py +0 -0
- {django_pfx-1.7.3.dev12 → django_pfx-1.7.3.dev16}/pfx/pfxcore/sms/backends/base.py +0 -0
- {django_pfx-1.7.3.dev12 → django_pfx-1.7.3.dev16}/pfx/pfxcore/sms/backends/console.py +0 -0
- {django_pfx-1.7.3.dev12 → django_pfx-1.7.3.dev16}/pfx/pfxcore/storage/__init__.py +0 -0
- {django_pfx-1.7.3.dev12 → django_pfx-1.7.3.dev16}/pfx/pfxcore/storage/exceptions.py +0 -0
- {django_pfx-1.7.3.dev12 → django_pfx-1.7.3.dev16}/pfx/pfxcore/storage/local_storage.py +0 -0
- {django_pfx-1.7.3.dev12 → django_pfx-1.7.3.dev16}/pfx/pfxcore/storage/s3_storage.py +0 -0
- {django_pfx-1.7.3.dev12 → django_pfx-1.7.3.dev16}/pfx/pfxcore/templates/registration/otp_code_email.txt +0 -0
- {django_pfx-1.7.3.dev12 → django_pfx-1.7.3.dev16}/pfx/pfxcore/templates/registration/otp_code_subject.txt +0 -0
- {django_pfx-1.7.3.dev12 → django_pfx-1.7.3.dev16}/pfx/pfxcore/templates/registration/password_reset_email.txt +0 -0
- {django_pfx-1.7.3.dev12 → django_pfx-1.7.3.dev16}/pfx/pfxcore/templates/registration/password_reset_subject.txt +0 -0
- {django_pfx-1.7.3.dev12 → django_pfx-1.7.3.dev16}/pfx/pfxcore/templates/registration/welcome_email.txt +0 -0
- {django_pfx-1.7.3.dev12 → django_pfx-1.7.3.dev16}/pfx/pfxcore/templates/registration/welcome_subject.txt +0 -0
- {django_pfx-1.7.3.dev12 → django_pfx-1.7.3.dev16}/pfx/pfxcore/test.py +0 -0
- {django_pfx-1.7.3.dev12 → django_pfx-1.7.3.dev16}/pfx/pfxcore/urls.py +0 -0
- {django_pfx-1.7.3.dev12 → django_pfx-1.7.3.dev16}/pfx/pfxcore/views/__init__.py +0 -0
- {django_pfx-1.7.3.dev12 → django_pfx-1.7.3.dev16}/pfx/pfxcore/views/fields.py +0 -0
- {django_pfx-1.7.3.dev12 → django_pfx-1.7.3.dev16}/pfx/pfxcore/views/filters_views.py +0 -0
- {django_pfx-1.7.3.dev12 → django_pfx-1.7.3.dev16}/pfx/pfxcore/views/locale_views.py +0 -0
- {django_pfx-1.7.3.dev12 → django_pfx-1.7.3.dev16}/pfx/pfxcore/views/media_rest_view_mixin.py +0 -0
- {django_pfx-1.7.3.dev12 → django_pfx-1.7.3.dev16}/pfx/pfxcore/views/ordered_rest_view_mixin.py +0 -0
- {django_pfx-1.7.3.dev12 → django_pfx-1.7.3.dev16}/pfx/pfxcore/views/parameters/__init__.py +0 -0
- {django_pfx-1.7.3.dev12 → django_pfx-1.7.3.dev16}/pfx/pfxcore/views/parameters/date_format.py +0 -0
- {django_pfx-1.7.3.dev12 → django_pfx-1.7.3.dev16}/pfx/pfxcore/views/parameters/groups.py +0 -0
- {django_pfx-1.7.3.dev12 → django_pfx-1.7.3.dev16}/pfx/pfxcore/views/parameters/list_count.py +0 -0
- {django_pfx-1.7.3.dev12 → django_pfx-1.7.3.dev16}/pfx/pfxcore/views/parameters/list_items.py +0 -0
- {django_pfx-1.7.3.dev12 → django_pfx-1.7.3.dev16}/pfx/pfxcore/views/parameters/list_mode.py +0 -0
- {django_pfx-1.7.3.dev12 → django_pfx-1.7.3.dev16}/pfx/pfxcore/views/parameters/list_order.py +0 -0
- {django_pfx-1.7.3.dev12 → django_pfx-1.7.3.dev16}/pfx/pfxcore/views/parameters/list_search.py +0 -0
- {django_pfx-1.7.3.dev12 → django_pfx-1.7.3.dev16}/pfx/pfxcore/views/parameters/media_redirect.py +0 -0
- {django_pfx-1.7.3.dev12 → django_pfx-1.7.3.dev16}/pfx/pfxcore/views/parameters/meta_fields.py +0 -0
- {django_pfx-1.7.3.dev12 → django_pfx-1.7.3.dev16}/pfx/pfxcore/views/parameters/meta_filters.py +0 -0
- {django_pfx-1.7.3.dev12 → django_pfx-1.7.3.dev16}/pfx/pfxcore/views/parameters/meta_orders.py +0 -0
- {django_pfx-1.7.3.dev12 → django_pfx-1.7.3.dev16}/pfx/pfxcore/views/parameters/subset.py +0 -0
- {django_pfx-1.7.3.dev12 → django_pfx-1.7.3.dev16}/pfx/pfxcore/views/parameters/subset_limit.py +0 -0
- {django_pfx-1.7.3.dev12 → django_pfx-1.7.3.dev16}/pfx/pfxcore/views/parameters/subset_offset.py +0 -0
- {django_pfx-1.7.3.dev12 → django_pfx-1.7.3.dev16}/pfx/pfxcore/views/parameters/subset_page.py +0 -0
- {django_pfx-1.7.3.dev12 → django_pfx-1.7.3.dev16}/pfx/pfxcore/views/parameters/subset_page_size.py +0 -0
- {django_pfx-1.7.3.dev12 → django_pfx-1.7.3.dev16}/pfx/pfxcore/views/parameters/subset_page_subset.py +0 -0
- {django_pfx-1.7.3.dev12 → django_pfx-1.7.3.dev16}/pfx/pfxcore/views/rest_views.py +0 -0
- {django_pfx-1.7.3.dev12 → django_pfx-1.7.3.dev16}/pfx/settings/__init__.py +0 -0
- {django_pfx-1.7.3.dev12 → django_pfx-1.7.3.dev16}/pfx/settings/dev.py +0 -0
- {django_pfx-1.7.3.dev12 → django_pfx-1.7.3.dev16}/pfx/userjobs/__init__.py +0 -0
- {django_pfx-1.7.3.dev12 → django_pfx-1.7.3.dev16}/pfx/userjobs/apps.py +0 -0
- {django_pfx-1.7.3.dev12 → django_pfx-1.7.3.dev16}/pfx/userjobs/locale/fr/LC_MESSAGES/django.mo +0 -0
- {django_pfx-1.7.3.dev12 → django_pfx-1.7.3.dev16}/pfx/userjobs/locale/fr/LC_MESSAGES/django.po +0 -0
- {django_pfx-1.7.3.dev12 → django_pfx-1.7.3.dev16}/pfx/userjobs/migrations/0001_initial.py +0 -0
- {django_pfx-1.7.3.dev12 → django_pfx-1.7.3.dev16}/pfx/userjobs/migrations/0002_alter_userjob_options_alter_userjob_auto_queued_and_more.py +0 -0
- {django_pfx-1.7.3.dev12 → django_pfx-1.7.3.dev16}/pfx/userjobs/migrations/__init__.py +0 -0
- {django_pfx-1.7.3.dev12 → django_pfx-1.7.3.dev16}/pfx/userjobs/models/__init__.py +0 -0
- {django_pfx-1.7.3.dev12 → django_pfx-1.7.3.dev16}/pfx/userjobs/models/user_job.py +0 -0
- {django_pfx-1.7.3.dev12 → django_pfx-1.7.3.dev16}/pfx/userjobs/models/user_job_attachment.py +0 -0
- {django_pfx-1.7.3.dev12 → django_pfx-1.7.3.dev16}/pfx/userjobs/providers/__init__.py +0 -0
- {django_pfx-1.7.3.dev12 → django_pfx-1.7.3.dev16}/pfx/userjobs/providers/user_job_django_q_executor.py +0 -0
- {django_pfx-1.7.3.dev12 → django_pfx-1.7.3.dev16}/pfx/userjobs/providers/user_job_websocket_channel_provider.py +0 -0
- {django_pfx-1.7.3.dev12 → django_pfx-1.7.3.dev16}/pfx/userjobs/tasks.py +0 -0
- {django_pfx-1.7.3.dev12 → django_pfx-1.7.3.dev16}/pfx/userjobs/urls.py +0 -0
- {django_pfx-1.7.3.dev12 → django_pfx-1.7.3.dev16}/pfx/userjobs/views/__init__.py +0 -0
- {django_pfx-1.7.3.dev12 → django_pfx-1.7.3.dev16}/pfx/userjobs/views/user_job_attachment_view.py +0 -0
- {django_pfx-1.7.3.dev12 → django_pfx-1.7.3.dev16}/pfx/userjobs/views/user_job_view.py +0 -0
- {django_pfx-1.7.3.dev12 → django_pfx-1.7.3.dev16}/pfx/userjobs/views/user_websocket_consumer.py +0 -0
- {django_pfx-1.7.3.dev12 → django_pfx-1.7.3.dev16}/pyproject.toml +0 -0
- {django_pfx-1.7.3.dev12 → django_pfx-1.7.3.dev16}/serve-doc +0 -0
- {django_pfx-1.7.3.dev12 → django_pfx-1.7.3.dev16}/setup.cfg +0 -0
- {django_pfx-1.7.3.dev12 → django_pfx-1.7.3.dev16}/setup.py +0 -0
- {django_pfx-1.7.3.dev12 → django_pfx-1.7.3.dev16}/tests/__init__.py +0 -0
- {django_pfx-1.7.3.dev12 → django_pfx-1.7.3.dev16}/tests/apps.py +0 -0
- {django_pfx-1.7.3.dev12 → django_pfx-1.7.3.dev16}/tests/channel_group_send_calls.py +0 -0
- {django_pfx-1.7.3.dev12 → django_pfx-1.7.3.dev16}/tests/locale/fr/LC_MESSAGES/django.po +0 -0
- {django_pfx-1.7.3.dev12 → django_pfx-1.7.3.dev16}/tests/migrations/0001_initial.py +0 -0
- {django_pfx-1.7.3.dev12 → django_pfx-1.7.3.dev16}/tests/migrations/0002_alter_book_cover.py +0 -0
- {django_pfx-1.7.3.dev12 → django_pfx-1.7.3.dev16}/tests/migrations/0003_book_local_file.py +0 -0
- {django_pfx-1.7.3.dev12 → django_pfx-1.7.3.dev16}/tests/migrations/0004_mfausermixin_fields.py +0 -0
- {django_pfx-1.7.3.dev12 → django_pfx-1.7.3.dev16}/tests/migrations/0005_mfausermixin_fields_fix.py +0 -0
- {django_pfx-1.7.3.dev12 → django_pfx-1.7.3.dev16}/tests/migrations/0006_rename_otp_enabled_user_mfa_authenticator_enabled_and_more.py +0 -0
- {django_pfx-1.7.3.dev12 → django_pfx-1.7.3.dev16}/tests/migrations/0007_attachment.py +0 -0
- {django_pfx-1.7.3.dev12 → django_pfx-1.7.3.dev16}/tests/migrations/__init__.py +0 -0
- {django_pfx-1.7.3.dev12 → django_pfx-1.7.3.dev16}/tests/models.py +0 -0
- {django_pfx-1.7.3.dev12 → django_pfx-1.7.3.dev16}/tests/settings/__init__.py +0 -0
- {django_pfx-1.7.3.dev12 → django_pfx-1.7.3.dev16}/tests/settings/ci.py +0 -0
- {django_pfx-1.7.3.dev12 → django_pfx-1.7.3.dev16}/tests/settings/common.py +0 -0
- {django_pfx-1.7.3.dev12 → django_pfx-1.7.3.dev16}/tests/settings/dev.py +0 -0
- {django_pfx-1.7.3.dev12 → django_pfx-1.7.3.dev16}/tests/settings/dev_custom_example.py +0 -0
- {django_pfx-1.7.3.dev12 → django_pfx-1.7.3.dev16}/tests/settings/dev_default.py +0 -0
- {django_pfx-1.7.3.dev12 → django_pfx-1.7.3.dev16}/tests/tests/basic_api_errors.py +0 -0
- {django_pfx-1.7.3.dev12 → django_pfx-1.7.3.dev16}/tests/tests/basic_api_test.py +0 -0
- {django_pfx-1.7.3.dev12 → django_pfx-1.7.3.dev16}/tests/tests/test_api_doc.py +0 -0
- {django_pfx-1.7.3.dev12 → django_pfx-1.7.3.dev16}/tests/tests/test_api_doc_search.py +0 -0
- {django_pfx-1.7.3.dev12 → django_pfx-1.7.3.dev16}/tests/tests/test_body_mixin.py +0 -0
- {django_pfx-1.7.3.dev12 → django_pfx-1.7.3.dev16}/tests/tests/test_cache.py +0 -0
- {django_pfx-1.7.3.dev12 → django_pfx-1.7.3.dev16}/tests/tests/test_client.py +0 -0
- {django_pfx-1.7.3.dev12 → django_pfx-1.7.3.dev16}/tests/tests/test_emails_email_api.py +0 -0
- {django_pfx-1.7.3.dev12 → django_pfx-1.7.3.dev16}/tests/tests/test_emails_email_model.py +0 -0
- {django_pfx-1.7.3.dev12 → django_pfx-1.7.3.dev16}/tests/tests/test_emails_q_mailjet_email_backend.py +0 -0
- {django_pfx-1.7.3.dev12 → django_pfx-1.7.3.dev16}/tests/tests/test_emails_send_email_message.py +0 -0
- {django_pfx-1.7.3.dev12 → django_pfx-1.7.3.dev16}/tests/tests/test_fields_choices.py +0 -0
- {django_pfx-1.7.3.dev12 → django_pfx-1.7.3.dev16}/tests/tests/test_fields_date.py +0 -0
- {django_pfx-1.7.3.dev12 → django_pfx-1.7.3.dev16}/tests/tests/test_fields_decimal.py +0 -0
- {django_pfx-1.7.3.dev12 → django_pfx-1.7.3.dev16}/tests/tests/test_fields_minutes_duration.py +0 -0
- {django_pfx-1.7.3.dev12 → django_pfx-1.7.3.dev16}/tests/tests/test_fields_one2many.py +0 -0
- {django_pfx-1.7.3.dev12 → django_pfx-1.7.3.dev16}/tests/tests/test_fields_rich_text.py +0 -0
- {django_pfx-1.7.3.dev12 → django_pfx-1.7.3.dev16}/tests/tests/test_fields_time.py +0 -0
- {django_pfx-1.7.3.dev12 → django_pfx-1.7.3.dev16}/tests/tests/test_filters.py +0 -0
- {django_pfx-1.7.3.dev12 → django_pfx-1.7.3.dev16}/tests/tests/test_locale_api.py +0 -0
- {django_pfx-1.7.3.dev12 → django_pfx-1.7.3.dev16}/tests/tests/test_ordered_rest_view_mixin.py +0 -0
- {django_pfx-1.7.3.dev12 → django_pfx-1.7.3.dev16}/tests/tests/test_perm_tests.py +0 -0
- {django_pfx-1.7.3.dev12 → django_pfx-1.7.3.dev16}/tests/tests/test_permissions.py +0 -0
- {django_pfx-1.7.3.dev12 → django_pfx-1.7.3.dev16}/tests/tests/test_perms_api.py +0 -0
- {django_pfx-1.7.3.dev12 → django_pfx-1.7.3.dev16}/tests/tests/test_post_migrate_groups_update.py +0 -0
- {django_pfx-1.7.3.dev12 → django_pfx-1.7.3.dev16}/tests/tests/test_profiling_middleware.py +0 -0
- {django_pfx-1.7.3.dev12 → django_pfx-1.7.3.dev16}/tests/tests/test_settings.py +0 -0
- {django_pfx-1.7.3.dev12 → django_pfx-1.7.3.dev16}/tests/tests/test_shortcuts.py +0 -0
- {django_pfx-1.7.3.dev12 → django_pfx-1.7.3.dev16}/tests/tests/test_timezone_middleware.py +0 -0
- {django_pfx-1.7.3.dev12 → django_pfx-1.7.3.dev16}/tests/tests/test_tools.py +0 -0
- {django_pfx-1.7.3.dev12 → django_pfx-1.7.3.dev16}/tests/tests/test_user_job_api.py +0 -0
- {django_pfx-1.7.3.dev12 → django_pfx-1.7.3.dev16}/tests/tests/test_user_job_messages.py +0 -0
- {django_pfx-1.7.3.dev12 → django_pfx-1.7.3.dev16}/tests/tests/test_user_job_model.py +0 -0
- {django_pfx-1.7.3.dev12 → django_pfx-1.7.3.dev16}/tests/tests/test_user_queryset.py +0 -0
- {django_pfx-1.7.3.dev12 → django_pfx-1.7.3.dev16}/tests/tests/test_view_decorators.py +0 -0
- {django_pfx-1.7.3.dev12 → django_pfx-1.7.3.dev16}/tests/tests/test_view_fields.py +0 -0
- {django_pfx-1.7.3.dev12 → django_pfx-1.7.3.dev16}/tests/urls.py +0 -0
- {django_pfx-1.7.3.dev12 → django_pfx-1.7.3.dev16}/tests/views.py +0 -0
- {django_pfx-1.7.3.dev12 → django_pfx-1.7.3.dev16}/tests_base_user/__init__.py +0 -0
- {django_pfx-1.7.3.dev12 → django_pfx-1.7.3.dev16}/tests_base_user/migrations/0001_initial.py +0 -0
- {django_pfx-1.7.3.dev12 → django_pfx-1.7.3.dev16}/tests_base_user/migrations/__init__.py +0 -0
- {django_pfx-1.7.3.dev12 → django_pfx-1.7.3.dev16}/tests_base_user/settings/__init__.py +0 -0
- {django_pfx-1.7.3.dev12 → django_pfx-1.7.3.dev16}/tests_base_user/settings/ci.py +0 -0
- {django_pfx-1.7.3.dev12 → django_pfx-1.7.3.dev16}/tests_base_user/settings/common.py +0 -0
- {django_pfx-1.7.3.dev12 → django_pfx-1.7.3.dev16}/tests_base_user/settings/dev.py +0 -0
- {django_pfx-1.7.3.dev12 → django_pfx-1.7.3.dev16}/tests_base_user/settings/dev_custom_example.py +0 -0
- {django_pfx-1.7.3.dev12 → django_pfx-1.7.3.dev16}/tests_base_user/settings/dev_default.py +0 -0
- {django_pfx-1.7.3.dev12 → django_pfx-1.7.3.dev16}/tests_base_user/tests/__init__.py +0 -0
- {django_pfx-1.7.3.dev12 → django_pfx-1.7.3.dev16}/tests_base_user/tests/test_api.py +0 -0
- {django_pfx-1.7.3.dev12 → django_pfx-1.7.3.dev16}/tests_base_user/tests/test_auth_api.py +0 -0
- {django_pfx-1.7.3.dev12 → django_pfx-1.7.3.dev16}/tests_base_user/urls.py +0 -0
- {django_pfx-1.7.3.dev12 → django_pfx-1.7.3.dev16}/tests_base_user/views.py +0 -0
- {django_pfx-1.7.3.dev12 → django_pfx-1.7.3.dev16}/tests_custom_user/__init__.py +0 -0
- {django_pfx-1.7.3.dev12 → django_pfx-1.7.3.dev16}/tests_custom_user/migrations/0001_initial.py +0 -0
- {django_pfx-1.7.3.dev12 → django_pfx-1.7.3.dev16}/tests_custom_user/migrations/__init__.py +0 -0
- {django_pfx-1.7.3.dev12 → django_pfx-1.7.3.dev16}/tests_custom_user/models.py +0 -0
- {django_pfx-1.7.3.dev12 → django_pfx-1.7.3.dev16}/tests_custom_user/settings/__init__.py +0 -0
- {django_pfx-1.7.3.dev12 → django_pfx-1.7.3.dev16}/tests_custom_user/settings/ci.py +0 -0
- {django_pfx-1.7.3.dev12 → django_pfx-1.7.3.dev16}/tests_custom_user/settings/common.py +0 -0
- {django_pfx-1.7.3.dev12 → django_pfx-1.7.3.dev16}/tests_custom_user/settings/dev.py +0 -0
- {django_pfx-1.7.3.dev12 → django_pfx-1.7.3.dev16}/tests_custom_user/settings/dev_custom_example.py +0 -0
- {django_pfx-1.7.3.dev12 → django_pfx-1.7.3.dev16}/tests_custom_user/settings/dev_default.py +0 -0
- {django_pfx-1.7.3.dev12 → django_pfx-1.7.3.dev16}/tests_custom_user/tests/__init__.py +0 -0
- {django_pfx-1.7.3.dev12 → django_pfx-1.7.3.dev16}/tests_custom_user/tests/test_api.py +0 -0
- {django_pfx-1.7.3.dev12 → django_pfx-1.7.3.dev16}/tests_custom_user/tests/test_auth_api.py +0 -0
- {django_pfx-1.7.3.dev12 → django_pfx-1.7.3.dev16}/tests_custom_user/urls.py +0 -0
- {django_pfx-1.7.3.dev12 → django_pfx-1.7.3.dev16}/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.dev16
|
|
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.dev16
|
|
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
|
|
@@ -89,6 +89,7 @@ pfx/pfxcore/middleware/authentication.py
|
|
|
89
89
|
pfx/pfxcore/middleware/locale.py
|
|
90
90
|
pfx/pfxcore/middleware/profiling.py
|
|
91
91
|
pfx/pfxcore/middleware/request_too_big_middleware.py
|
|
92
|
+
pfx/pfxcore/middleware/ws_cookie_authentication_middleware.py
|
|
92
93
|
pfx/pfxcore/migrations/0001_initial.py
|
|
93
94
|
pfx/pfxcore/migrations/0002_pfxpermissionsuser.py
|
|
94
95
|
pfx/pfxcore/migrations/0003_delete_pfxpermissionsuser.py
|
|
@@ -227,6 +228,7 @@ tests/tests/test_user_job_api.py
|
|
|
227
228
|
tests/tests/test_user_job_messages.py
|
|
228
229
|
tests/tests/test_user_job_model.py
|
|
229
230
|
tests/tests/test_user_queryset.py
|
|
231
|
+
tests/tests/test_user_websocket.py
|
|
230
232
|
tests/tests/test_view_decorators.py
|
|
231
233
|
tests/tests/test_view_fields.py
|
|
232
234
|
tests_base_user/__init__.py
|
|
@@ -119,9 +119,9 @@ class MyUser(MFAUserMixin, AbstractPFXBaseUser):
|
|
|
119
119
|
pass
|
|
120
120
|
```
|
|
121
121
|
|
|
122
|
-
|
|
122
|
+
```{note}
|
|
123
123
|
`OtpUserMixin` is a deprecated alias for `MFAUserMixin` and will be removed in a future version.
|
|
124
|
-
|
|
124
|
+
```
|
|
125
125
|
|
|
126
126
|
#### Settings
|
|
127
127
|
|
|
@@ -140,7 +140,7 @@ class MyUser(MFAUserMixin, AbstractPFXBaseUser):
|
|
|
140
140
|
* `PFX_OTP_IMAGE`: An image https URL used by FreeOTP. See [FreeOTP URI](https://github.com/npmccallum/freeotp-android/blob/master/URI.md).
|
|
141
141
|
* `PFX_OTP_COLOR`: A brand color (in RRGGBB format) for used by FreeOTP. See [FreeOTP URI](https://github.com/npmccallum/freeotp-android/blob/master/URI.md).
|
|
142
142
|
|
|
143
|
-
The user can then
|
|
143
|
+
The user can then manage MFA methods using the [services documented below](#mfa-setup-services).
|
|
144
144
|
|
|
145
145
|
## Services
|
|
146
146
|
|
|
@@ -281,53 +281,197 @@ Request a new challenge code or switch to a different OOB backend (email or SMS)
|
|
|
281
281
|
| token | the jwt token. (only if mode is `jwt`) |
|
|
282
282
|
| user | the user object |
|
|
283
283
|
|
|
284
|
-
###
|
|
285
|
-
Services to enable the MFA with OTP.
|
|
286
|
-
You have to call first the `setup-uri` service to get the URI,
|
|
287
|
-
encode it as a QR code and present it in the UI of your software.
|
|
288
|
-
Then user then scans this QR code to add the OTP secret to his OTP App.
|
|
289
|
-
Finally, the `enable` service must be called with an OTP code retrieved
|
|
290
|
-
in the OTP App to confirm the activation.
|
|
284
|
+
### MFA Setup Services
|
|
291
285
|
|
|
292
|
-
|
|
286
|
+
All three MFA methods follow the same `{setup, confirm, disable}` pattern under `/auth/mfa/{authenticator|email|sms}/`.
|
|
293
287
|
|
|
294
|
-
**
|
|
288
|
+
The **setup** step initiates enrollment and, for OOB backends (email/SMS), sends a challenge code.
|
|
289
|
+
The **confirm** step validates the code and activates the method, returning a fresh JWT token.
|
|
290
|
+
The **disable** step deactivates the method after verifying a valid OTP code; when the last active
|
|
291
|
+
method is disabled, the shared OTP secret is also deleted.
|
|
295
292
|
|
|
296
|
-
|
|
297
|
-
|
|
293
|
+
```{mermaid}
|
|
294
|
+
|
|
295
|
+
sequenceDiagram
|
|
296
|
+
participant App
|
|
297
|
+
participant API
|
|
298
|
+
App->>API: GET/PUT /auth/mfa/{method}/setup
|
|
299
|
+
API->>App: 200 — setup_uri (authenticator) or code sent (email/SMS)
|
|
300
|
+
App->>API: PUT /auth/mfa/{method}/confirm
|
|
301
|
+
note right of App: otp_code
|
|
302
|
+
alt Code valid
|
|
303
|
+
API->>App: 200 — new JWT token + updated user
|
|
304
|
+
else Code invalid
|
|
305
|
+
API->>App: 422 — otp_code error
|
|
306
|
+
end
|
|
307
|
+
|
|
308
|
+
```
|
|
309
|
+
|
|
310
|
+
#### List backends
|
|
311
|
+
|
|
312
|
+
**Request:** `GET` `/auth/mfa/backends`
|
|
313
|
+
|
|
314
|
+
Returns the list of backends configured in `PFX_MFA_BACKENDS` and their activation status for the
|
|
315
|
+
authenticated user.
|
|
316
|
+
|
|
317
|
+
**Response:** `HTTP 200`
|
|
318
|
+
|
|
319
|
+
```json
|
|
320
|
+
{
|
|
321
|
+
"backends": [
|
|
322
|
+
{"id": "authenticator", "is_oob": false, "enabled": true},
|
|
323
|
+
{"id": "email", "is_oob": true, "enabled": false}
|
|
324
|
+
]
|
|
325
|
+
}
|
|
326
|
+
```
|
|
327
|
+
|
|
328
|
+
#### Authenticator (TOTP)
|
|
329
|
+
|
|
330
|
+
**Request:** `GET` `/auth/mfa/authenticator/setup`
|
|
298
331
|
|
|
299
|
-
|
|
300
|
-
|-----------|---------------------------------------|
|
|
301
|
-
| setup_uri | the uri to enable the OTP application |
|
|
332
|
+
Generates a temporary OTP secret and returns the provisioning URI to encode as a QR code.
|
|
302
333
|
|
|
303
|
-
**
|
|
334
|
+
**Responses:**
|
|
335
|
+
|
|
336
|
+
* `HTTP 400` if authenticator MFA is already enabled
|
|
337
|
+
* `HTTP 200` with:
|
|
338
|
+
|
|
339
|
+
| Field | Description |
|
|
340
|
+
|-----------|------------------------------------------------------|
|
|
341
|
+
| setup_uri | `otpauth://` URI to scan with an authenticator app |
|
|
342
|
+
|
|
343
|
+
---
|
|
344
|
+
|
|
345
|
+
**Request:** `PUT` `/auth/mfa/authenticator/confirm`
|
|
346
|
+
|
|
347
|
+
Confirms authenticator activation with a TOTP code scanned from the QR code.
|
|
304
348
|
|
|
305
349
|
**Request body:**
|
|
306
350
|
|
|
307
|
-
| Field
|
|
308
|
-
|
|
309
|
-
| otp_code
|
|
351
|
+
| Field | Description |
|
|
352
|
+
|----------|----------------------------------------|
|
|
353
|
+
| otp_code | TOTP code from the authenticator app |
|
|
310
354
|
|
|
311
|
-
**Responses
|
|
355
|
+
**Responses:**
|
|
356
|
+
|
|
357
|
+
* `HTTP 422` if the code is invalid
|
|
358
|
+
* `HTTP 200` with a new JWT token and updated user object
|
|
312
359
|
|
|
313
|
-
|
|
314
|
-
* `HTTP 200` if the otp code is valid
|
|
360
|
+
---
|
|
315
361
|
|
|
316
|
-
|
|
317
|
-
A service to disable the MFA with OTP.
|
|
362
|
+
**Request:** `PUT` `/auth/mfa/authenticator/disable`
|
|
318
363
|
|
|
319
|
-
|
|
364
|
+
Disables authenticator MFA. Requires a valid OTP code (TOTP or HOTP).
|
|
320
365
|
|
|
321
366
|
**Request body:**
|
|
322
367
|
|
|
323
|
-
| Field
|
|
324
|
-
|
|
325
|
-
| otp_code
|
|
368
|
+
| Field | Description |
|
|
369
|
+
|----------|-------------------|
|
|
370
|
+
| otp_code | a valid OTP code |
|
|
326
371
|
|
|
327
|
-
**Responses
|
|
372
|
+
**Responses:**
|
|
373
|
+
|
|
374
|
+
* `HTTP 422` if the code is invalid
|
|
375
|
+
* `HTTP 200` with a new JWT token and updated user object
|
|
376
|
+
|
|
377
|
+
#### Email
|
|
378
|
+
|
|
379
|
+
**Request:** `PUT` `/auth/mfa/email/setup`
|
|
380
|
+
|
|
381
|
+
Starts email MFA enrollment. If no shared secret exists yet, a temporary one is generated.
|
|
382
|
+
A HOTP challenge code is sent to the user's email address.
|
|
383
|
+
|
|
384
|
+
**Responses:**
|
|
385
|
+
|
|
386
|
+
* `HTTP 400` if email MFA is already enabled
|
|
387
|
+
* `HTTP 200` with a confirmation message
|
|
388
|
+
|
|
389
|
+
---
|
|
390
|
+
|
|
391
|
+
**Request:** `PUT` `/auth/mfa/email/confirm`
|
|
392
|
+
|
|
393
|
+
Confirms email MFA activation with the code received by email.
|
|
394
|
+
On success the temporary secret is promoted to the permanent shared secret.
|
|
395
|
+
|
|
396
|
+
**Request body:**
|
|
397
|
+
|
|
398
|
+
| Field | Description |
|
|
399
|
+
|----------|-----------------------------------|
|
|
400
|
+
| otp_code | the code received by email |
|
|
401
|
+
|
|
402
|
+
**Responses:**
|
|
403
|
+
|
|
404
|
+
* `HTTP 422` if the code is invalid
|
|
405
|
+
* `HTTP 200` with a new JWT token and updated user object
|
|
406
|
+
|
|
407
|
+
---
|
|
408
|
+
|
|
409
|
+
**Request:** `PUT` `/auth/mfa/email/disable`
|
|
410
|
+
|
|
411
|
+
Disables email MFA. Requires a valid OTP code (TOTP or HOTP).
|
|
412
|
+
|
|
413
|
+
**Request body:**
|
|
414
|
+
|
|
415
|
+
| Field | Description |
|
|
416
|
+
|----------|-------------------|
|
|
417
|
+
| otp_code | a valid OTP code |
|
|
418
|
+
|
|
419
|
+
**Responses:**
|
|
420
|
+
|
|
421
|
+
* `HTTP 422` if the code is invalid
|
|
422
|
+
* `HTTP 200` with a new JWT token and updated user object
|
|
423
|
+
|
|
424
|
+
#### SMS
|
|
425
|
+
|
|
426
|
+
```{note}
|
|
427
|
+
`sms_phone_number` must be set on the user before calling the setup endpoint.
|
|
428
|
+
`sms` must also be included in `PFX_MFA_BACKENDS` and `PFX_SMS_BACKEND` must point to a real
|
|
429
|
+
SMS provider for production use.
|
|
430
|
+
```
|
|
431
|
+
|
|
432
|
+
**Request:** `PUT` `/auth/mfa/sms/setup`
|
|
433
|
+
|
|
434
|
+
Starts SMS MFA enrollment. A HOTP challenge code is sent to `sms_phone_number`.
|
|
435
|
+
|
|
436
|
+
**Responses:**
|
|
437
|
+
|
|
438
|
+
* `HTTP 422` if `sms_phone_number` is not set on the user
|
|
439
|
+
* `HTTP 400` if SMS MFA is already enabled
|
|
440
|
+
* `HTTP 200` with a confirmation message
|
|
441
|
+
|
|
442
|
+
---
|
|
443
|
+
|
|
444
|
+
**Request:** `PUT` `/auth/mfa/sms/confirm`
|
|
445
|
+
|
|
446
|
+
Confirms SMS MFA activation with the code received by SMS.
|
|
447
|
+
|
|
448
|
+
**Request body:**
|
|
449
|
+
|
|
450
|
+
| Field | Description |
|
|
451
|
+
|----------|----------------------------|
|
|
452
|
+
| otp_code | the code received by SMS |
|
|
453
|
+
|
|
454
|
+
**Responses:**
|
|
455
|
+
|
|
456
|
+
* `HTTP 422` if the code is invalid
|
|
457
|
+
* `HTTP 200` with a new JWT token and updated user object
|
|
458
|
+
|
|
459
|
+
---
|
|
460
|
+
|
|
461
|
+
**Request:** `PUT` `/auth/mfa/sms/disable`
|
|
462
|
+
|
|
463
|
+
Disables SMS MFA. Requires a valid OTP code (TOTP or HOTP).
|
|
464
|
+
|
|
465
|
+
**Request body:**
|
|
466
|
+
|
|
467
|
+
| Field | Description |
|
|
468
|
+
|----------|-------------------|
|
|
469
|
+
| otp_code | a valid OTP code |
|
|
470
|
+
|
|
471
|
+
**Responses:**
|
|
328
472
|
|
|
329
|
-
* `HTTP 422` if the
|
|
330
|
-
* `HTTP 200`
|
|
473
|
+
* `HTTP 422` if the code is invalid
|
|
474
|
+
* `HTTP 200` with a new JWT token and updated user object
|
|
331
475
|
|
|
332
476
|
|
|
333
477
|
### Logout
|
|
Binary file
|
{django_pfx-1.7.3.dev12 → django_pfx-1.7.3.dev16}/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-06-08 15:57+0200\n"
|
|
11
11
|
"PO-Revision-Date: 2021-06-22 23:31+0200\n"
|
|
12
12
|
"Last-Translator: \n"
|
|
13
13
|
"Language-Team: \n"
|
|
@@ -25,8 +25,8 @@ msgstr "Une erreur interne du serveur est survenue."
|
|
|
25
25
|
#: default_settings.py:51
|
|
26
26
|
msgid "A system error occurred. Our support team has been notified."
|
|
27
27
|
msgstr ""
|
|
28
|
-
"Une erreur système s’est produite. "
|
|
29
|
-
"
|
|
28
|
+
"Une erreur système s’est produite. Notre équipe d’assistance en a été "
|
|
29
|
+
"informée."
|
|
30
30
|
|
|
31
31
|
#: exceptions.py:53 exceptions.py:76
|
|
32
32
|
#, python-brace-format
|
|
@@ -121,11 +121,11 @@ msgstr "Numéro de téléphone (SMS)"
|
|
|
121
121
|
msgid "Temporary SMS phone number"
|
|
122
122
|
msgstr "Numéro de téléphone temporaire (SMS)"
|
|
123
123
|
|
|
124
|
-
#: models/mfa_user_mixin.py:35
|
|
124
|
+
#: models/mfa_user_mixin.py:35 views/authentication_views.py:650
|
|
125
125
|
msgid "SMS MFA enabled"
|
|
126
126
|
msgstr "MFA SMS activé"
|
|
127
127
|
|
|
128
|
-
#: models/mfa_user_mixin.py:38
|
|
128
|
+
#: models/mfa_user_mixin.py:38 views/authentication_views.py:532
|
|
129
129
|
msgid "Email MFA enabled"
|
|
130
130
|
msgstr "MFA par email activé"
|
|
131
131
|
|
|
@@ -133,7 +133,7 @@ msgstr "MFA par email activé"
|
|
|
133
133
|
msgid "MFA setup dismissed"
|
|
134
134
|
msgstr "Configuration MFA annulée"
|
|
135
135
|
|
|
136
|
-
#: models/mfa_user_mixin.py:44
|
|
136
|
+
#: models/mfa_user_mixin.py:44 views/authentication_views.py:1007
|
|
137
137
|
msgid "Authenticator MFA enabled"
|
|
138
138
|
msgstr "Application MFA activée"
|
|
139
139
|
|
|
@@ -273,51 +273,70 @@ msgstr ""
|
|
|
273
273
|
msgid "Successful login"
|
|
274
274
|
msgstr "Connexion réussie"
|
|
275
275
|
|
|
276
|
-
#: views/authentication_views.py:
|
|
276
|
+
#: views/authentication_views.py:399
|
|
277
277
|
msgid "This field is required."
|
|
278
278
|
msgstr "Ce champ est requis."
|
|
279
279
|
|
|
280
|
-
#: views/authentication_views.py:
|
|
280
|
+
#: views/authentication_views.py:410 views/authentication_views.py:494
|
|
281
|
+
#: views/authentication_views.py:612
|
|
281
282
|
msgid "Code sent."
|
|
282
283
|
msgstr "Code envoyé."
|
|
283
284
|
|
|
284
|
-
#: views/authentication_views.py:
|
|
285
|
+
#: views/authentication_views.py:491
|
|
286
|
+
msgid "Email MFA is already enabled"
|
|
287
|
+
msgstr "MFA par email déjà activé."
|
|
288
|
+
|
|
289
|
+
#: views/authentication_views.py:534 views/authentication_views.py:569
|
|
290
|
+
#: views/authentication_views.py:652 views/authentication_views.py:687
|
|
291
|
+
#: views/authentication_views.py:1009 views/authentication_views.py:1048
|
|
292
|
+
msgid "Invalid code"
|
|
293
|
+
msgstr "Code invalide"
|
|
294
|
+
|
|
295
|
+
#: views/authentication_views.py:573
|
|
296
|
+
msgid "Email MFA disabled"
|
|
297
|
+
msgstr "MFA par email désactivé"
|
|
298
|
+
|
|
299
|
+
#: views/authentication_views.py:606
|
|
300
|
+
msgid "Phone number is required."
|
|
301
|
+
msgstr "Le numéro de téléphone est requis."
|
|
302
|
+
|
|
303
|
+
#: views/authentication_views.py:609
|
|
304
|
+
msgid "SMS MFA is already enabled"
|
|
305
|
+
msgstr "MFA par SMS déjà activé."
|
|
306
|
+
|
|
307
|
+
#: views/authentication_views.py:691
|
|
308
|
+
msgid "SMS MFA disabled"
|
|
309
|
+
msgstr "MFA par SMS désactivé"
|
|
310
|
+
|
|
311
|
+
#: views/authentication_views.py:754 views/authentication_views.py:922
|
|
285
312
|
msgid "password updated successfully"
|
|
286
313
|
msgstr "le mot de passe a été mis à jour avec succès"
|
|
287
314
|
|
|
288
|
-
#: views/authentication_views.py:
|
|
315
|
+
#: views/authentication_views.py:759
|
|
289
316
|
msgid "Incorrect password"
|
|
290
317
|
msgstr "Mot de passe incorrect"
|
|
291
318
|
|
|
292
|
-
#: views/authentication_views.py:
|
|
319
|
+
#: views/authentication_views.py:762 views/authentication_views.py:931
|
|
293
320
|
msgid "Empty password is not allowed"
|
|
294
321
|
msgstr "Un mot de passe vide n’est pas autorisé"
|
|
295
322
|
|
|
296
|
-
#: views/authentication_views.py:
|
|
323
|
+
#: views/authentication_views.py:851
|
|
297
324
|
msgid "User and token are valid"
|
|
298
325
|
msgstr "L’utilisateur et le token sont valides"
|
|
299
326
|
|
|
300
|
-
#: views/authentication_views.py:
|
|
327
|
+
#: views/authentication_views.py:853
|
|
301
328
|
msgid "User or token is invalid"
|
|
302
329
|
msgstr "L’utilisateur ou le token est invalide"
|
|
303
330
|
|
|
304
|
-
#: views/authentication_views.py:
|
|
305
|
-
msgid "
|
|
306
|
-
msgstr "
|
|
307
|
-
|
|
308
|
-
#: views/authentication_views.py:711
|
|
309
|
-
msgid "OTP enabled"
|
|
310
|
-
msgstr "OTP activé"
|
|
331
|
+
#: views/authentication_views.py:967
|
|
332
|
+
msgid "Authenticator MFA is already enabled"
|
|
333
|
+
msgstr "MFA par application déjà activé"
|
|
311
334
|
|
|
312
|
-
#: views/authentication_views.py:
|
|
313
|
-
msgid "
|
|
314
|
-
msgstr "
|
|
335
|
+
#: views/authentication_views.py:1046
|
|
336
|
+
msgid "Authenticator MFA disabled"
|
|
337
|
+
msgstr "MFA par application activée"
|
|
315
338
|
|
|
316
|
-
#: views/authentication_views.py:
|
|
317
|
-
msgid "OTP disabled"
|
|
318
|
-
msgstr "OTP désactivé"
|
|
319
|
-
|
|
320
|
-
#: views/authentication_views.py:1008
|
|
339
|
+
#: views/authentication_views.py:1308
|
|
321
340
|
msgid ""
|
|
322
341
|
"If the email address you entered is correct, you will receive an email from "
|
|
323
342
|
"us with instructions to reset your password."
|
|
@@ -326,7 +345,7 @@ msgstr ""
|
|
|
326
345
|
"un courrier électronique de notre part contenant des instructions pour "
|
|
327
346
|
"réinitialiser votre mot de passe."
|
|
328
347
|
|
|
329
|
-
#: views/authentication_views.py:
|
|
348
|
+
#: views/authentication_views.py:1382
|
|
330
349
|
msgid "A new authentication code has been sent by email."
|
|
331
350
|
msgstr "Un nouveau code d’authentification a été envoyé par e-mail."
|
|
332
351
|
|
|
@@ -369,3 +388,9 @@ msgstr "{model} {obj} modifié."
|
|
|
369
388
|
#, python-brace-format
|
|
370
389
|
msgid "{model} {obj} deleted."
|
|
371
390
|
msgstr "{model} {obj} supprimé."
|
|
391
|
+
|
|
392
|
+
#~ msgid "OTP enabled"
|
|
393
|
+
#~ msgstr "OTP activé"
|
|
394
|
+
|
|
395
|
+
#~ msgid "OTP disabled"
|
|
396
|
+
#~ msgstr "OTP désactivé"
|
|
@@ -5,3 +5,6 @@ from .authentication import (
|
|
|
5
5
|
from .locale import LocaleMiddleware
|
|
6
6
|
from .profiling import ProfilingDatabaseRouter, ProfilingMiddleware
|
|
7
7
|
from .request_too_big_middleware import RequestTooBigMiddleware
|
|
8
|
+
from .ws_cookie_authentication_middleware import (
|
|
9
|
+
WSCookieAuthenticationMiddleware,
|
|
10
|
+
)
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
|
|
3
|
+
from django.contrib.auth import get_user_model
|
|
4
|
+
from django.contrib.auth.models import AnonymousUser
|
|
5
|
+
from django.http import parse_cookie
|
|
6
|
+
|
|
7
|
+
from channels.db import database_sync_to_async
|
|
8
|
+
from channels.middleware import BaseMiddleware
|
|
9
|
+
|
|
10
|
+
from pfx.pfxcore.exceptions import APIError
|
|
11
|
+
from pfx.pfxcore.http import JsonResponse
|
|
12
|
+
from pfx.pfxcore.middleware.authentication import JWTTokenDecodeMixin
|
|
13
|
+
|
|
14
|
+
logger = logging.getLogger(__name__)
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class WSCookieAuthenticationMiddleware(JWTTokenDecodeMixin, BaseMiddleware):
|
|
18
|
+
async def __call__(self, scope, receive, send):
|
|
19
|
+
scope = dict(scope)
|
|
20
|
+
if "headers" not in scope:
|
|
21
|
+
raise ValueError("No headers")
|
|
22
|
+
# Go through headers to find the cookie one
|
|
23
|
+
for name, value in scope.get("headers", []):
|
|
24
|
+
if name == b"cookie":
|
|
25
|
+
cookies = parse_cookie(value.decode("utf-8"))
|
|
26
|
+
break
|
|
27
|
+
else:
|
|
28
|
+
# No cookie header found - add an empty default.
|
|
29
|
+
cookies = {}
|
|
30
|
+
# Return inner application
|
|
31
|
+
key = cookies.get('token')
|
|
32
|
+
if key:
|
|
33
|
+
try:
|
|
34
|
+
user, __, __ = await database_sync_to_async(
|
|
35
|
+
self.decode_jwt)(key)
|
|
36
|
+
except get_user_model().DoesNotExist:
|
|
37
|
+
return JsonResponse(
|
|
38
|
+
{'message': 'Authentication error'}, status=401)
|
|
39
|
+
except APIError as e:
|
|
40
|
+
return e.response
|
|
41
|
+
elif not scope.get('user'):
|
|
42
|
+
user = AnonymousUser()
|
|
43
|
+
|
|
44
|
+
scope['user'] = user
|
|
45
|
+
return await super().__call__(scope, receive, send)
|
|
@@ -82,24 +82,76 @@ class MFAUserMixin(models.Model):
|
|
|
82
82
|
"""Add extra attribute to response when need_otp=True."""
|
|
83
83
|
return {}
|
|
84
84
|
|
|
85
|
+
# --- private helpers ---
|
|
86
|
+
|
|
87
|
+
def _clear_secret_if_no_mfa(self):
|
|
88
|
+
"""Delete otp_secret_token when no MFA method remains active."""
|
|
89
|
+
if not (self.mfa_authenticator_enabled
|
|
90
|
+
or self.mfa_sms_enabled
|
|
91
|
+
or self.mfa_email_enabled):
|
|
92
|
+
self.otp_secret_token = None
|
|
93
|
+
self.save(update_fields=['otp_secret_token'])
|
|
94
|
+
|
|
95
|
+
def _prepare_oob_mfa_activation(self):
|
|
96
|
+
"""Prepare an OOB (email or SMS) MFA activation.
|
|
97
|
+
|
|
98
|
+
If no permanent secret exists yet, generates ``otp_secret_token_tmp``
|
|
99
|
+
so the JWT signature key is not changed before the user confirms.
|
|
100
|
+
Returns the HOTP code to send to the user.
|
|
101
|
+
"""
|
|
102
|
+
import pyotp
|
|
103
|
+
if not self.otp_secret_token:
|
|
104
|
+
self.otp_secret_token_tmp = pyotp.random_base32()
|
|
105
|
+
self.save(update_fields=['otp_secret_token_tmp'])
|
|
106
|
+
secret = self.otp_secret_token_tmp or self.otp_secret_token
|
|
107
|
+
self.hotp_count += 1
|
|
108
|
+
self.hotp_expiry = timezone.now() + timedelta(
|
|
109
|
+
minutes=settings.PFX_HOTP_CODE_VALIDITY)
|
|
110
|
+
self.save(update_fields=['hotp_count', 'hotp_expiry'])
|
|
111
|
+
return pyotp.hotp.HOTP(secret).at(self.hotp_count)
|
|
112
|
+
|
|
113
|
+
def _confirm_oob_mfa_activation(self, otp_code, flag_field):
|
|
114
|
+
"""Confirm an OOB MFA activation.
|
|
115
|
+
|
|
116
|
+
Validates the code against the temporary secret (first-time setup) or
|
|
117
|
+
the permanent one (adding a second method). On success promotes the
|
|
118
|
+
temporary secret when applicable and sets the method flag.
|
|
119
|
+
|
|
120
|
+
:param otp_code: Code entered by the user.
|
|
121
|
+
:param flag_field: Model field name to set to ``True`` on success.
|
|
122
|
+
:returns: ``True`` on success, ``False`` if the code is invalid.
|
|
123
|
+
"""
|
|
124
|
+
use_tmp = bool(self.otp_secret_token_tmp)
|
|
125
|
+
if self.is_otp_valid(otp_code, tmp=use_tmp):
|
|
126
|
+
if use_tmp:
|
|
127
|
+
self.otp_secret_token = self.otp_secret_token_tmp
|
|
128
|
+
self.otp_secret_token_tmp = None
|
|
129
|
+
setattr(self, flag_field, True)
|
|
130
|
+
self.save(update_fields=[
|
|
131
|
+
'otp_secret_token', 'otp_secret_token_tmp', flag_field])
|
|
132
|
+
else:
|
|
133
|
+
setattr(self, flag_field, True)
|
|
134
|
+
self.save(update_fields=[flag_field])
|
|
135
|
+
return True
|
|
136
|
+
return False
|
|
137
|
+
|
|
138
|
+
# --- authenticator (TOTP) ---
|
|
139
|
+
|
|
85
140
|
def enable_otp(self):
|
|
86
|
-
"""
|
|
141
|
+
"""Start authenticator setup.
|
|
87
142
|
|
|
88
|
-
Generates a new temporary OTP secret
|
|
89
|
-
|
|
143
|
+
Generates a new temporary OTP secret. Call :meth:`confirm_otp`
|
|
144
|
+
with a valid TOTP code to complete activation.
|
|
90
145
|
"""
|
|
91
146
|
import pyotp
|
|
92
147
|
self.otp_secret_token_tmp = pyotp.random_base32()
|
|
93
148
|
self.save(update_fields=['otp_secret_token_tmp'])
|
|
94
149
|
|
|
95
150
|
def confirm_otp(self, otp_code):
|
|
96
|
-
"""Confirm
|
|
97
|
-
|
|
98
|
-
Set the OTP secret token from the temporary one if the provided
|
|
99
|
-
code is valid.
|
|
151
|
+
"""Confirm authenticator activation.
|
|
100
152
|
|
|
101
|
-
:param otp_code: A valid
|
|
102
|
-
:returns:
|
|
153
|
+
:param otp_code: A valid TOTP code for the temporary secret.
|
|
154
|
+
:returns: ``True`` on success, ``False`` otherwise.
|
|
103
155
|
"""
|
|
104
156
|
if self.is_otp_valid(otp_code, tmp=True):
|
|
105
157
|
self.otp_secret_token = self.otp_secret_token_tmp
|
|
@@ -112,14 +164,67 @@ class MFAUserMixin(models.Model):
|
|
|
112
164
|
return False
|
|
113
165
|
|
|
114
166
|
def disable_otp(self):
|
|
115
|
-
"""Disable
|
|
167
|
+
"""Disable authenticator MFA.
|
|
116
168
|
|
|
117
|
-
|
|
169
|
+
Clears the permanent secret only when no other method remains active.
|
|
118
170
|
"""
|
|
119
|
-
self.otp_secret_token = None
|
|
120
171
|
self.mfa_authenticator_enabled = False
|
|
121
|
-
self.save(update_fields=[
|
|
122
|
-
|
|
172
|
+
self.save(update_fields=['mfa_authenticator_enabled'])
|
|
173
|
+
self._clear_secret_if_no_mfa()
|
|
174
|
+
|
|
175
|
+
# --- email ---
|
|
176
|
+
|
|
177
|
+
def enable_email_mfa(self):
|
|
178
|
+
"""Start email MFA activation.
|
|
179
|
+
|
|
180
|
+
:returns: The HOTP code to send to the user by email.
|
|
181
|
+
"""
|
|
182
|
+
return self._prepare_oob_mfa_activation()
|
|
183
|
+
|
|
184
|
+
def confirm_email_mfa(self, otp_code):
|
|
185
|
+
"""Confirm email MFA activation.
|
|
186
|
+
|
|
187
|
+
:param otp_code: Code received by the user.
|
|
188
|
+
:returns: ``True`` on success, ``False`` if the code is invalid.
|
|
189
|
+
"""
|
|
190
|
+
return self._confirm_oob_mfa_activation(otp_code, 'mfa_email_enabled')
|
|
191
|
+
|
|
192
|
+
def disable_email_mfa(self):
|
|
193
|
+
"""Disable email MFA.
|
|
194
|
+
|
|
195
|
+
Clears the permanent secret when this was the last active method.
|
|
196
|
+
"""
|
|
197
|
+
self.mfa_email_enabled = False
|
|
198
|
+
self.save(update_fields=['mfa_email_enabled'])
|
|
199
|
+
self._clear_secret_if_no_mfa()
|
|
200
|
+
|
|
201
|
+
# --- SMS ---
|
|
202
|
+
|
|
203
|
+
def enable_sms_mfa(self):
|
|
204
|
+
"""Start SMS MFA activation.
|
|
205
|
+
|
|
206
|
+
:returns: The HOTP code to send to the user by SMS.
|
|
207
|
+
"""
|
|
208
|
+
return self._prepare_oob_mfa_activation()
|
|
209
|
+
|
|
210
|
+
def confirm_sms_mfa(self, otp_code):
|
|
211
|
+
"""Confirm SMS MFA activation.
|
|
212
|
+
|
|
213
|
+
:param otp_code: Code received by the user.
|
|
214
|
+
:returns: ``True`` on success, ``False`` if the code is invalid.
|
|
215
|
+
"""
|
|
216
|
+
return self._confirm_oob_mfa_activation(otp_code, 'mfa_sms_enabled')
|
|
217
|
+
|
|
218
|
+
def disable_sms_mfa(self):
|
|
219
|
+
"""Disable SMS MFA.
|
|
220
|
+
|
|
221
|
+
Clears the permanent secret when this was the last active method.
|
|
222
|
+
"""
|
|
223
|
+
self.mfa_sms_enabled = False
|
|
224
|
+
self.save(update_fields=['mfa_sms_enabled'])
|
|
225
|
+
self._clear_secret_if_no_mfa()
|
|
226
|
+
|
|
227
|
+
# --- shared OTP helpers ---
|
|
123
228
|
|
|
124
229
|
def get_otp_setup_uri(self, tmp=False, with_color=True):
|
|
125
230
|
"""Return the setup URL for OTP activation.
|