django-pfx 1.7.3.dev14__tar.gz → 1.7.3.dev18__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.
Files changed (265) hide show
  1. {django_pfx-1.7.3.dev14 → django_pfx-1.7.3.dev18}/PKG-INFO +1 -1
  2. {django_pfx-1.7.3.dev14 → django_pfx-1.7.3.dev18}/django_pfx.egg-info/PKG-INFO +1 -1
  3. {django_pfx-1.7.3.dev14 → django_pfx-1.7.3.dev18}/doc/source/authentication.md +177 -33
  4. django_pfx-1.7.3.dev18/pfx/pfxcore/locale/fr/LC_MESSAGES/django.mo +0 -0
  5. {django_pfx-1.7.3.dev14 → django_pfx-1.7.3.dev18}/pfx/pfxcore/locale/fr/LC_MESSAGES/django.po +54 -29
  6. django_pfx-1.7.3.dev18/pfx/pfxcore/middleware/ws_cookie_authentication_middleware.py +35 -0
  7. {django_pfx-1.7.3.dev14 → django_pfx-1.7.3.dev18}/pfx/pfxcore/models/mfa_user_mixin.py +119 -14
  8. {django_pfx-1.7.3.dev14 → django_pfx-1.7.3.dev18}/pfx/pfxcore/views/authentication_views.py +339 -39
  9. {django_pfx-1.7.3.dev14 → django_pfx-1.7.3.dev18}/pfx/pfxcore/views/rest_views.py +1 -1
  10. {django_pfx-1.7.3.dev14 → django_pfx-1.7.3.dev18}/requirements.txt +1 -1
  11. {django_pfx-1.7.3.dev14 → django_pfx-1.7.3.dev18}/tests/tests/test_auth_api.py +488 -118
  12. django_pfx-1.7.3.dev14/pfx/pfxcore/locale/fr/LC_MESSAGES/django.mo +0 -0
  13. django_pfx-1.7.3.dev14/pfx/pfxcore/middleware/ws_cookie_authentication_middleware.py +0 -45
  14. {django_pfx-1.7.3.dev14 → django_pfx-1.7.3.dev18}/.gitignore +0 -0
  15. {django_pfx-1.7.3.dev14 → django_pfx-1.7.3.dev18}/.gitlab-ci.yml +0 -0
  16. {django_pfx-1.7.3.dev14 → django_pfx-1.7.3.dev18}/.pre-commit-config.yaml +0 -0
  17. {django_pfx-1.7.3.dev14 → django_pfx-1.7.3.dev18}/LICENSE +0 -0
  18. {django_pfx-1.7.3.dev14 → django_pfx-1.7.3.dev18}/MANIFEST.in +0 -0
  19. {django_pfx-1.7.3.dev14 → django_pfx-1.7.3.dev18}/README.md +0 -0
  20. {django_pfx-1.7.3.dev14 → django_pfx-1.7.3.dev18}/django_pfx.egg-info/SOURCES.txt +0 -0
  21. {django_pfx-1.7.3.dev14 → django_pfx-1.7.3.dev18}/django_pfx.egg-info/dependency_links.txt +0 -0
  22. {django_pfx-1.7.3.dev14 → django_pfx-1.7.3.dev18}/django_pfx.egg-info/requires.txt +0 -0
  23. {django_pfx-1.7.3.dev14 → django_pfx-1.7.3.dev18}/django_pfx.egg-info/top_level.txt +0 -0
  24. {django_pfx-1.7.3.dev14 → django_pfx-1.7.3.dev18}/doc/Makefile +0 -0
  25. {django_pfx-1.7.3.dev14 → django_pfx-1.7.3.dev18}/doc/conf.py +0 -0
  26. {django_pfx-1.7.3.dev14 → django_pfx-1.7.3.dev18}/doc/index.rst +0 -0
  27. {django_pfx-1.7.3.dev14 → django_pfx-1.7.3.dev18}/doc/source/api.views.rst +0 -0
  28. {django_pfx-1.7.3.dev14 → django_pfx-1.7.3.dev18}/doc/source/decorator.md +0 -0
  29. {django_pfx-1.7.3.dev14 → django_pfx-1.7.3.dev18}/doc/source/generate_openapi.md +0 -0
  30. {django_pfx-1.7.3.dev14 → django_pfx-1.7.3.dev18}/doc/source/getting_started.md +0 -0
  31. {django_pfx-1.7.3.dev14 → django_pfx-1.7.3.dev18}/doc/source/internationalisation.md +0 -0
  32. {django_pfx-1.7.3.dev14 → django_pfx-1.7.3.dev18}/doc/source/model.md +0 -0
  33. {django_pfx-1.7.3.dev14 → django_pfx-1.7.3.dev18}/doc/source/pfx_views.md +0 -0
  34. {django_pfx-1.7.3.dev14 → django_pfx-1.7.3.dev18}/doc/source/profiling.md +0 -0
  35. {django_pfx-1.7.3.dev14 → django_pfx-1.7.3.dev18}/doc/source/settings.md +0 -0
  36. {django_pfx-1.7.3.dev14 → django_pfx-1.7.3.dev18}/doc/source/testing.md +0 -0
  37. {django_pfx-1.7.3.dev14 → django_pfx-1.7.3.dev18}/img/pfx.png +0 -0
  38. {django_pfx-1.7.3.dev14 → django_pfx-1.7.3.dev18}/img/pfx.svg +0 -0
  39. {django_pfx-1.7.3.dev14 → django_pfx-1.7.3.dev18}/make_messages +0 -0
  40. {django_pfx-1.7.3.dev14 → django_pfx-1.7.3.dev18}/manage.py +0 -0
  41. {django_pfx-1.7.3.dev14 → django_pfx-1.7.3.dev18}/pfx/__init__.py +0 -0
  42. {django_pfx-1.7.3.dev14 → django_pfx-1.7.3.dev18}/pfx/emails/__init__.py +0 -0
  43. {django_pfx-1.7.3.dev14 → django_pfx-1.7.3.dev18}/pfx/emails/apps.py +0 -0
  44. {django_pfx-1.7.3.dev14 → django_pfx-1.7.3.dev18}/pfx/emails/email_backend/__init__.py +0 -0
  45. {django_pfx-1.7.3.dev14 → django_pfx-1.7.3.dev18}/pfx/emails/email_backend/q_mailjet_email_backend.py +0 -0
  46. {django_pfx-1.7.3.dev14 → django_pfx-1.7.3.dev18}/pfx/emails/exceptions.py +0 -0
  47. {django_pfx-1.7.3.dev14 → django_pfx-1.7.3.dev18}/pfx/emails/mailjet/__init__.py +0 -0
  48. {django_pfx-1.7.3.dev14 → django_pfx-1.7.3.dev18}/pfx/emails/mailjet/mailjet.py +0 -0
  49. {django_pfx-1.7.3.dev14 → django_pfx-1.7.3.dev18}/pfx/emails/mailjet/message_result.py +0 -0
  50. {django_pfx-1.7.3.dev14 → django_pfx-1.7.3.dev18}/pfx/emails/migrations/0001_initial.py +0 -0
  51. {django_pfx-1.7.3.dev14 → django_pfx-1.7.3.dev18}/pfx/emails/migrations/0002_alter_email_headers.py +0 -0
  52. {django_pfx-1.7.3.dev14 → django_pfx-1.7.3.dev18}/pfx/emails/migrations/0003_alter_email_options.py +0 -0
  53. {django_pfx-1.7.3.dev14 → django_pfx-1.7.3.dev18}/pfx/emails/migrations/__init__.py +0 -0
  54. {django_pfx-1.7.3.dev14 → django_pfx-1.7.3.dev18}/pfx/emails/models/__init__.py +0 -0
  55. {django_pfx-1.7.3.dev14 → django_pfx-1.7.3.dev18}/pfx/emails/models/email.py +0 -0
  56. {django_pfx-1.7.3.dev14 → django_pfx-1.7.3.dev18}/pfx/emails/providers/__init__.py +0 -0
  57. {django_pfx-1.7.3.dev14 → django_pfx-1.7.3.dev18}/pfx/emails/providers/email_django_q_executor.py +0 -0
  58. {django_pfx-1.7.3.dev14 → django_pfx-1.7.3.dev18}/pfx/emails/shortcuts.py +0 -0
  59. {django_pfx-1.7.3.dev14 → django_pfx-1.7.3.dev18}/pfx/emails/tasks.py +0 -0
  60. {django_pfx-1.7.3.dev14 → django_pfx-1.7.3.dev18}/pfx/emails/templates/emails/base_template.html +0 -0
  61. {django_pfx-1.7.3.dev14 → django_pfx-1.7.3.dev18}/pfx/emails/templates/emails/base_template.txt +0 -0
  62. {django_pfx-1.7.3.dev14 → django_pfx-1.7.3.dev18}/pfx/emails/templates/emails/base_template_subject.txt +0 -0
  63. {django_pfx-1.7.3.dev14 → django_pfx-1.7.3.dev18}/pfx/emails/urls.py +0 -0
  64. {django_pfx-1.7.3.dev14 → django_pfx-1.7.3.dev18}/pfx/emails/views/__init__.py +0 -0
  65. {django_pfx-1.7.3.dev14 → django_pfx-1.7.3.dev18}/pfx/emails/views/email_rest_view.py +0 -0
  66. {django_pfx-1.7.3.dev14 → django_pfx-1.7.3.dev18}/pfx/pfxcore/__init__.py +0 -0
  67. {django_pfx-1.7.3.dev14 → django_pfx-1.7.3.dev18}/pfx/pfxcore/apidoc/__init__.py +0 -0
  68. {django_pfx-1.7.3.dev14 → django_pfx-1.7.3.dev18}/pfx/pfxcore/apidoc/parameters.py +0 -0
  69. {django_pfx-1.7.3.dev14 → django_pfx-1.7.3.dev18}/pfx/pfxcore/apidoc/schema.py +0 -0
  70. {django_pfx-1.7.3.dev14 → django_pfx-1.7.3.dev18}/pfx/pfxcore/apidoc/tags.py +0 -0
  71. {django_pfx-1.7.3.dev14 → django_pfx-1.7.3.dev18}/pfx/pfxcore/apps.py +0 -0
  72. {django_pfx-1.7.3.dev14 → django_pfx-1.7.3.dev18}/pfx/pfxcore/decorator/__init__.py +0 -0
  73. {django_pfx-1.7.3.dev14 → django_pfx-1.7.3.dev18}/pfx/pfxcore/decorator/rest.py +0 -0
  74. {django_pfx-1.7.3.dev14 → django_pfx-1.7.3.dev18}/pfx/pfxcore/default_settings.py +0 -0
  75. {django_pfx-1.7.3.dev14 → django_pfx-1.7.3.dev18}/pfx/pfxcore/exceptions.py +0 -0
  76. {django_pfx-1.7.3.dev14 → django_pfx-1.7.3.dev18}/pfx/pfxcore/fields/__init__.py +0 -0
  77. {django_pfx-1.7.3.dev14 → django_pfx-1.7.3.dev18}/pfx/pfxcore/fields/decimal_field.py +0 -0
  78. {django_pfx-1.7.3.dev14 → django_pfx-1.7.3.dev18}/pfx/pfxcore/fields/media_field.py +0 -0
  79. {django_pfx-1.7.3.dev14 → django_pfx-1.7.3.dev18}/pfx/pfxcore/fields/minutes_duration_field.py +0 -0
  80. {django_pfx-1.7.3.dev14 → django_pfx-1.7.3.dev18}/pfx/pfxcore/fields/rich_text_field.py +0 -0
  81. {django_pfx-1.7.3.dev14 → django_pfx-1.7.3.dev18}/pfx/pfxcore/http/__init__.py +0 -0
  82. {django_pfx-1.7.3.dev14 → django_pfx-1.7.3.dev18}/pfx/pfxcore/http/json_response.py +0 -0
  83. {django_pfx-1.7.3.dev14 → django_pfx-1.7.3.dev18}/pfx/pfxcore/management/__init__.py +0 -0
  84. {django_pfx-1.7.3.dev14 → django_pfx-1.7.3.dev18}/pfx/pfxcore/management/commands/__init__.py +0 -0
  85. {django_pfx-1.7.3.dev14 → django_pfx-1.7.3.dev18}/pfx/pfxcore/management/commands/makeapidoc.py +0 -0
  86. {django_pfx-1.7.3.dev14 → django_pfx-1.7.3.dev18}/pfx/pfxcore/management/commands/profile.py +0 -0
  87. {django_pfx-1.7.3.dev14 → django_pfx-1.7.3.dev18}/pfx/pfxcore/middleware/__init__.py +0 -0
  88. {django_pfx-1.7.3.dev14 → django_pfx-1.7.3.dev18}/pfx/pfxcore/middleware/authentication.py +0 -0
  89. {django_pfx-1.7.3.dev14 → django_pfx-1.7.3.dev18}/pfx/pfxcore/middleware/locale.py +0 -0
  90. {django_pfx-1.7.3.dev14 → django_pfx-1.7.3.dev18}/pfx/pfxcore/middleware/profiling.py +0 -0
  91. {django_pfx-1.7.3.dev14 → django_pfx-1.7.3.dev18}/pfx/pfxcore/middleware/request_too_big_middleware.py +0 -0
  92. {django_pfx-1.7.3.dev14 → django_pfx-1.7.3.dev18}/pfx/pfxcore/migrations/0001_initial.py +0 -0
  93. {django_pfx-1.7.3.dev14 → django_pfx-1.7.3.dev18}/pfx/pfxcore/migrations/0002_pfxpermissionsuser.py +0 -0
  94. {django_pfx-1.7.3.dev14 → django_pfx-1.7.3.dev18}/pfx/pfxcore/migrations/0003_delete_pfxpermissionsuser.py +0 -0
  95. {django_pfx-1.7.3.dev14 → django_pfx-1.7.3.dev18}/pfx/pfxcore/migrations/0004_alter_loginban_failed_counter_and_more.py +0 -0
  96. {django_pfx-1.7.3.dev14 → django_pfx-1.7.3.dev18}/pfx/pfxcore/migrations/__init__.py +0 -0
  97. {django_pfx-1.7.3.dev14 → django_pfx-1.7.3.dev18}/pfx/pfxcore/migrations/operations/__init__.py +0 -0
  98. {django_pfx-1.7.3.dev14 → django_pfx-1.7.3.dev18}/pfx/pfxcore/migrations/operations/permissions.py +0 -0
  99. {django_pfx-1.7.3.dev14 → django_pfx-1.7.3.dev18}/pfx/pfxcore/models/__init__.py +0 -0
  100. {django_pfx-1.7.3.dev14 → django_pfx-1.7.3.dev18}/pfx/pfxcore/models/abstract_pfx_base_user.py +0 -0
  101. {django_pfx-1.7.3.dev14 → django_pfx-1.7.3.dev18}/pfx/pfxcore/models/attachment_mixin.py +0 -0
  102. {django_pfx-1.7.3.dev14 → django_pfx-1.7.3.dev18}/pfx/pfxcore/models/cache_mixins.py +0 -0
  103. {django_pfx-1.7.3.dev14 → django_pfx-1.7.3.dev18}/pfx/pfxcore/models/login_ban.py +0 -0
  104. {django_pfx-1.7.3.dev14 → django_pfx-1.7.3.dev18}/pfx/pfxcore/models/not_null_fields.py +0 -0
  105. {django_pfx-1.7.3.dev14 → django_pfx-1.7.3.dev18}/pfx/pfxcore/models/ordered_model_mixin.py +0 -0
  106. {django_pfx-1.7.3.dev14 → django_pfx-1.7.3.dev18}/pfx/pfxcore/models/pfx_models.py +0 -0
  107. {django_pfx-1.7.3.dev14 → django_pfx-1.7.3.dev18}/pfx/pfxcore/models/pfx_user.py +0 -0
  108. {django_pfx-1.7.3.dev14 → django_pfx-1.7.3.dev18}/pfx/pfxcore/models/user_filtered_queryset_mixin.py +0 -0
  109. {django_pfx-1.7.3.dev14 → django_pfx-1.7.3.dev18}/pfx/pfxcore/serializers/__init__.py +0 -0
  110. {django_pfx-1.7.3.dev14 → django_pfx-1.7.3.dev18}/pfx/pfxcore/serializers/json.py +0 -0
  111. {django_pfx-1.7.3.dev14 → django_pfx-1.7.3.dev18}/pfx/pfxcore/settings.py +0 -0
  112. {django_pfx-1.7.3.dev14 → django_pfx-1.7.3.dev18}/pfx/pfxcore/shortcuts.py +0 -0
  113. {django_pfx-1.7.3.dev14 → django_pfx-1.7.3.dev18}/pfx/pfxcore/sms/__init__.py +0 -0
  114. {django_pfx-1.7.3.dev14 → django_pfx-1.7.3.dev18}/pfx/pfxcore/sms/backends/__init__.py +0 -0
  115. {django_pfx-1.7.3.dev14 → django_pfx-1.7.3.dev18}/pfx/pfxcore/sms/backends/base.py +0 -0
  116. {django_pfx-1.7.3.dev14 → django_pfx-1.7.3.dev18}/pfx/pfxcore/sms/backends/console.py +0 -0
  117. {django_pfx-1.7.3.dev14 → django_pfx-1.7.3.dev18}/pfx/pfxcore/storage/__init__.py +0 -0
  118. {django_pfx-1.7.3.dev14 → django_pfx-1.7.3.dev18}/pfx/pfxcore/storage/exceptions.py +0 -0
  119. {django_pfx-1.7.3.dev14 → django_pfx-1.7.3.dev18}/pfx/pfxcore/storage/local_storage.py +0 -0
  120. {django_pfx-1.7.3.dev14 → django_pfx-1.7.3.dev18}/pfx/pfxcore/storage/s3_storage.py +0 -0
  121. {django_pfx-1.7.3.dev14 → django_pfx-1.7.3.dev18}/pfx/pfxcore/templates/registration/otp_code_email.txt +0 -0
  122. {django_pfx-1.7.3.dev14 → django_pfx-1.7.3.dev18}/pfx/pfxcore/templates/registration/otp_code_subject.txt +0 -0
  123. {django_pfx-1.7.3.dev14 → django_pfx-1.7.3.dev18}/pfx/pfxcore/templates/registration/password_reset_email.txt +0 -0
  124. {django_pfx-1.7.3.dev14 → django_pfx-1.7.3.dev18}/pfx/pfxcore/templates/registration/password_reset_subject.txt +0 -0
  125. {django_pfx-1.7.3.dev14 → django_pfx-1.7.3.dev18}/pfx/pfxcore/templates/registration/welcome_email.txt +0 -0
  126. {django_pfx-1.7.3.dev14 → django_pfx-1.7.3.dev18}/pfx/pfxcore/templates/registration/welcome_subject.txt +0 -0
  127. {django_pfx-1.7.3.dev14 → django_pfx-1.7.3.dev18}/pfx/pfxcore/test.py +0 -0
  128. {django_pfx-1.7.3.dev14 → django_pfx-1.7.3.dev18}/pfx/pfxcore/urls.py +0 -0
  129. {django_pfx-1.7.3.dev14 → django_pfx-1.7.3.dev18}/pfx/pfxcore/views/__init__.py +0 -0
  130. {django_pfx-1.7.3.dev14 → django_pfx-1.7.3.dev18}/pfx/pfxcore/views/fields.py +0 -0
  131. {django_pfx-1.7.3.dev14 → django_pfx-1.7.3.dev18}/pfx/pfxcore/views/filters_views.py +0 -0
  132. {django_pfx-1.7.3.dev14 → django_pfx-1.7.3.dev18}/pfx/pfxcore/views/locale_views.py +0 -0
  133. {django_pfx-1.7.3.dev14 → django_pfx-1.7.3.dev18}/pfx/pfxcore/views/media_rest_view_mixin.py +0 -0
  134. {django_pfx-1.7.3.dev14 → django_pfx-1.7.3.dev18}/pfx/pfxcore/views/ordered_rest_view_mixin.py +0 -0
  135. {django_pfx-1.7.3.dev14 → django_pfx-1.7.3.dev18}/pfx/pfxcore/views/parameters/__init__.py +0 -0
  136. {django_pfx-1.7.3.dev14 → django_pfx-1.7.3.dev18}/pfx/pfxcore/views/parameters/date_format.py +0 -0
  137. {django_pfx-1.7.3.dev14 → django_pfx-1.7.3.dev18}/pfx/pfxcore/views/parameters/groups.py +0 -0
  138. {django_pfx-1.7.3.dev14 → django_pfx-1.7.3.dev18}/pfx/pfxcore/views/parameters/list_count.py +0 -0
  139. {django_pfx-1.7.3.dev14 → django_pfx-1.7.3.dev18}/pfx/pfxcore/views/parameters/list_items.py +0 -0
  140. {django_pfx-1.7.3.dev14 → django_pfx-1.7.3.dev18}/pfx/pfxcore/views/parameters/list_mode.py +0 -0
  141. {django_pfx-1.7.3.dev14 → django_pfx-1.7.3.dev18}/pfx/pfxcore/views/parameters/list_order.py +0 -0
  142. {django_pfx-1.7.3.dev14 → django_pfx-1.7.3.dev18}/pfx/pfxcore/views/parameters/list_search.py +0 -0
  143. {django_pfx-1.7.3.dev14 → django_pfx-1.7.3.dev18}/pfx/pfxcore/views/parameters/media_redirect.py +0 -0
  144. {django_pfx-1.7.3.dev14 → django_pfx-1.7.3.dev18}/pfx/pfxcore/views/parameters/meta_fields.py +0 -0
  145. {django_pfx-1.7.3.dev14 → django_pfx-1.7.3.dev18}/pfx/pfxcore/views/parameters/meta_filters.py +0 -0
  146. {django_pfx-1.7.3.dev14 → django_pfx-1.7.3.dev18}/pfx/pfxcore/views/parameters/meta_orders.py +0 -0
  147. {django_pfx-1.7.3.dev14 → django_pfx-1.7.3.dev18}/pfx/pfxcore/views/parameters/subset.py +0 -0
  148. {django_pfx-1.7.3.dev14 → django_pfx-1.7.3.dev18}/pfx/pfxcore/views/parameters/subset_limit.py +0 -0
  149. {django_pfx-1.7.3.dev14 → django_pfx-1.7.3.dev18}/pfx/pfxcore/views/parameters/subset_offset.py +0 -0
  150. {django_pfx-1.7.3.dev14 → django_pfx-1.7.3.dev18}/pfx/pfxcore/views/parameters/subset_page.py +0 -0
  151. {django_pfx-1.7.3.dev14 → django_pfx-1.7.3.dev18}/pfx/pfxcore/views/parameters/subset_page_size.py +0 -0
  152. {django_pfx-1.7.3.dev14 → django_pfx-1.7.3.dev18}/pfx/pfxcore/views/parameters/subset_page_subset.py +0 -0
  153. {django_pfx-1.7.3.dev14 → django_pfx-1.7.3.dev18}/pfx/settings/__init__.py +0 -0
  154. {django_pfx-1.7.3.dev14 → django_pfx-1.7.3.dev18}/pfx/settings/dev.py +0 -0
  155. {django_pfx-1.7.3.dev14 → django_pfx-1.7.3.dev18}/pfx/userjobs/__init__.py +0 -0
  156. {django_pfx-1.7.3.dev14 → django_pfx-1.7.3.dev18}/pfx/userjobs/apps.py +0 -0
  157. {django_pfx-1.7.3.dev14 → django_pfx-1.7.3.dev18}/pfx/userjobs/locale/fr/LC_MESSAGES/django.mo +0 -0
  158. {django_pfx-1.7.3.dev14 → django_pfx-1.7.3.dev18}/pfx/userjobs/locale/fr/LC_MESSAGES/django.po +0 -0
  159. {django_pfx-1.7.3.dev14 → django_pfx-1.7.3.dev18}/pfx/userjobs/migrations/0001_initial.py +0 -0
  160. {django_pfx-1.7.3.dev14 → django_pfx-1.7.3.dev18}/pfx/userjobs/migrations/0002_alter_userjob_options_alter_userjob_auto_queued_and_more.py +0 -0
  161. {django_pfx-1.7.3.dev14 → django_pfx-1.7.3.dev18}/pfx/userjobs/migrations/__init__.py +0 -0
  162. {django_pfx-1.7.3.dev14 → django_pfx-1.7.3.dev18}/pfx/userjobs/models/__init__.py +0 -0
  163. {django_pfx-1.7.3.dev14 → django_pfx-1.7.3.dev18}/pfx/userjobs/models/user_job.py +0 -0
  164. {django_pfx-1.7.3.dev14 → django_pfx-1.7.3.dev18}/pfx/userjobs/models/user_job_attachment.py +0 -0
  165. {django_pfx-1.7.3.dev14 → django_pfx-1.7.3.dev18}/pfx/userjobs/providers/__init__.py +0 -0
  166. {django_pfx-1.7.3.dev14 → django_pfx-1.7.3.dev18}/pfx/userjobs/providers/user_job_django_q_executor.py +0 -0
  167. {django_pfx-1.7.3.dev14 → django_pfx-1.7.3.dev18}/pfx/userjobs/providers/user_job_websocket_channel_provider.py +0 -0
  168. {django_pfx-1.7.3.dev14 → django_pfx-1.7.3.dev18}/pfx/userjobs/tasks.py +0 -0
  169. {django_pfx-1.7.3.dev14 → django_pfx-1.7.3.dev18}/pfx/userjobs/urls.py +0 -0
  170. {django_pfx-1.7.3.dev14 → django_pfx-1.7.3.dev18}/pfx/userjobs/views/__init__.py +0 -0
  171. {django_pfx-1.7.3.dev14 → django_pfx-1.7.3.dev18}/pfx/userjobs/views/user_job_attachment_view.py +0 -0
  172. {django_pfx-1.7.3.dev14 → django_pfx-1.7.3.dev18}/pfx/userjobs/views/user_job_view.py +0 -0
  173. {django_pfx-1.7.3.dev14 → django_pfx-1.7.3.dev18}/pfx/userjobs/views/user_websocket_consumer.py +0 -0
  174. {django_pfx-1.7.3.dev14 → django_pfx-1.7.3.dev18}/pyproject.toml +0 -0
  175. {django_pfx-1.7.3.dev14 → django_pfx-1.7.3.dev18}/serve-doc +0 -0
  176. {django_pfx-1.7.3.dev14 → django_pfx-1.7.3.dev18}/setup.cfg +0 -0
  177. {django_pfx-1.7.3.dev14 → django_pfx-1.7.3.dev18}/setup.py +0 -0
  178. {django_pfx-1.7.3.dev14 → django_pfx-1.7.3.dev18}/tests/__init__.py +0 -0
  179. {django_pfx-1.7.3.dev14 → django_pfx-1.7.3.dev18}/tests/apps.py +0 -0
  180. {django_pfx-1.7.3.dev14 → django_pfx-1.7.3.dev18}/tests/channel_group_send_calls.py +0 -0
  181. {django_pfx-1.7.3.dev14 → django_pfx-1.7.3.dev18}/tests/locale/fr/LC_MESSAGES/django.po +0 -0
  182. {django_pfx-1.7.3.dev14 → django_pfx-1.7.3.dev18}/tests/migrations/0001_initial.py +0 -0
  183. {django_pfx-1.7.3.dev14 → django_pfx-1.7.3.dev18}/tests/migrations/0002_alter_book_cover.py +0 -0
  184. {django_pfx-1.7.3.dev14 → django_pfx-1.7.3.dev18}/tests/migrations/0003_book_local_file.py +0 -0
  185. {django_pfx-1.7.3.dev14 → django_pfx-1.7.3.dev18}/tests/migrations/0004_mfausermixin_fields.py +0 -0
  186. {django_pfx-1.7.3.dev14 → django_pfx-1.7.3.dev18}/tests/migrations/0005_mfausermixin_fields_fix.py +0 -0
  187. {django_pfx-1.7.3.dev14 → django_pfx-1.7.3.dev18}/tests/migrations/0006_rename_otp_enabled_user_mfa_authenticator_enabled_and_more.py +0 -0
  188. {django_pfx-1.7.3.dev14 → django_pfx-1.7.3.dev18}/tests/migrations/0007_attachment.py +0 -0
  189. {django_pfx-1.7.3.dev14 → django_pfx-1.7.3.dev18}/tests/migrations/__init__.py +0 -0
  190. {django_pfx-1.7.3.dev14 → django_pfx-1.7.3.dev18}/tests/models.py +0 -0
  191. {django_pfx-1.7.3.dev14 → django_pfx-1.7.3.dev18}/tests/settings/__init__.py +0 -0
  192. {django_pfx-1.7.3.dev14 → django_pfx-1.7.3.dev18}/tests/settings/ci.py +0 -0
  193. {django_pfx-1.7.3.dev14 → django_pfx-1.7.3.dev18}/tests/settings/common.py +0 -0
  194. {django_pfx-1.7.3.dev14 → django_pfx-1.7.3.dev18}/tests/settings/dev.py +0 -0
  195. {django_pfx-1.7.3.dev14 → django_pfx-1.7.3.dev18}/tests/settings/dev_custom_example.py +0 -0
  196. {django_pfx-1.7.3.dev14 → django_pfx-1.7.3.dev18}/tests/settings/dev_default.py +0 -0
  197. {django_pfx-1.7.3.dev14 → django_pfx-1.7.3.dev18}/tests/tests/__init__.py +0 -0
  198. {django_pfx-1.7.3.dev14 → django_pfx-1.7.3.dev18}/tests/tests/basic_api_errors.py +0 -0
  199. {django_pfx-1.7.3.dev14 → django_pfx-1.7.3.dev18}/tests/tests/basic_api_test.py +0 -0
  200. {django_pfx-1.7.3.dev14 → django_pfx-1.7.3.dev18}/tests/tests/test_api_doc.py +0 -0
  201. {django_pfx-1.7.3.dev14 → django_pfx-1.7.3.dev18}/tests/tests/test_api_doc_search.py +0 -0
  202. {django_pfx-1.7.3.dev14 → django_pfx-1.7.3.dev18}/tests/tests/test_body_mixin.py +0 -0
  203. {django_pfx-1.7.3.dev14 → django_pfx-1.7.3.dev18}/tests/tests/test_cache.py +0 -0
  204. {django_pfx-1.7.3.dev14 → django_pfx-1.7.3.dev18}/tests/tests/test_client.py +0 -0
  205. {django_pfx-1.7.3.dev14 → django_pfx-1.7.3.dev18}/tests/tests/test_emails_email_api.py +0 -0
  206. {django_pfx-1.7.3.dev14 → django_pfx-1.7.3.dev18}/tests/tests/test_emails_email_model.py +0 -0
  207. {django_pfx-1.7.3.dev14 → django_pfx-1.7.3.dev18}/tests/tests/test_emails_q_mailjet_email_backend.py +0 -0
  208. {django_pfx-1.7.3.dev14 → django_pfx-1.7.3.dev18}/tests/tests/test_emails_send_email_message.py +0 -0
  209. {django_pfx-1.7.3.dev14 → django_pfx-1.7.3.dev18}/tests/tests/test_fields_choices.py +0 -0
  210. {django_pfx-1.7.3.dev14 → django_pfx-1.7.3.dev18}/tests/tests/test_fields_date.py +0 -0
  211. {django_pfx-1.7.3.dev14 → django_pfx-1.7.3.dev18}/tests/tests/test_fields_decimal.py +0 -0
  212. {django_pfx-1.7.3.dev14 → django_pfx-1.7.3.dev18}/tests/tests/test_fields_minutes_duration.py +0 -0
  213. {django_pfx-1.7.3.dev14 → django_pfx-1.7.3.dev18}/tests/tests/test_fields_one2many.py +0 -0
  214. {django_pfx-1.7.3.dev14 → django_pfx-1.7.3.dev18}/tests/tests/test_fields_rich_text.py +0 -0
  215. {django_pfx-1.7.3.dev14 → django_pfx-1.7.3.dev18}/tests/tests/test_fields_time.py +0 -0
  216. {django_pfx-1.7.3.dev14 → django_pfx-1.7.3.dev18}/tests/tests/test_filters.py +0 -0
  217. {django_pfx-1.7.3.dev14 → django_pfx-1.7.3.dev18}/tests/tests/test_locale_api.py +0 -0
  218. {django_pfx-1.7.3.dev14 → django_pfx-1.7.3.dev18}/tests/tests/test_ordered_rest_view_mixin.py +0 -0
  219. {django_pfx-1.7.3.dev14 → django_pfx-1.7.3.dev18}/tests/tests/test_perm_tests.py +0 -0
  220. {django_pfx-1.7.3.dev14 → django_pfx-1.7.3.dev18}/tests/tests/test_permissions.py +0 -0
  221. {django_pfx-1.7.3.dev14 → django_pfx-1.7.3.dev18}/tests/tests/test_perms_api.py +0 -0
  222. {django_pfx-1.7.3.dev14 → django_pfx-1.7.3.dev18}/tests/tests/test_post_migrate_groups_update.py +0 -0
  223. {django_pfx-1.7.3.dev14 → django_pfx-1.7.3.dev18}/tests/tests/test_profiling_middleware.py +0 -0
  224. {django_pfx-1.7.3.dev14 → django_pfx-1.7.3.dev18}/tests/tests/test_settings.py +0 -0
  225. {django_pfx-1.7.3.dev14 → django_pfx-1.7.3.dev18}/tests/tests/test_shortcuts.py +0 -0
  226. {django_pfx-1.7.3.dev14 → django_pfx-1.7.3.dev18}/tests/tests/test_timezone_middleware.py +0 -0
  227. {django_pfx-1.7.3.dev14 → django_pfx-1.7.3.dev18}/tests/tests/test_tools.py +0 -0
  228. {django_pfx-1.7.3.dev14 → django_pfx-1.7.3.dev18}/tests/tests/test_user_job_api.py +0 -0
  229. {django_pfx-1.7.3.dev14 → django_pfx-1.7.3.dev18}/tests/tests/test_user_job_messages.py +0 -0
  230. {django_pfx-1.7.3.dev14 → django_pfx-1.7.3.dev18}/tests/tests/test_user_job_model.py +0 -0
  231. {django_pfx-1.7.3.dev14 → django_pfx-1.7.3.dev18}/tests/tests/test_user_queryset.py +0 -0
  232. {django_pfx-1.7.3.dev14 → django_pfx-1.7.3.dev18}/tests/tests/test_user_websocket.py +0 -0
  233. {django_pfx-1.7.3.dev14 → django_pfx-1.7.3.dev18}/tests/tests/test_view_decorators.py +0 -0
  234. {django_pfx-1.7.3.dev14 → django_pfx-1.7.3.dev18}/tests/tests/test_view_fields.py +0 -0
  235. {django_pfx-1.7.3.dev14 → django_pfx-1.7.3.dev18}/tests/urls.py +0 -0
  236. {django_pfx-1.7.3.dev14 → django_pfx-1.7.3.dev18}/tests/views.py +0 -0
  237. {django_pfx-1.7.3.dev14 → django_pfx-1.7.3.dev18}/tests_base_user/__init__.py +0 -0
  238. {django_pfx-1.7.3.dev14 → django_pfx-1.7.3.dev18}/tests_base_user/migrations/0001_initial.py +0 -0
  239. {django_pfx-1.7.3.dev14 → django_pfx-1.7.3.dev18}/tests_base_user/migrations/__init__.py +0 -0
  240. {django_pfx-1.7.3.dev14 → django_pfx-1.7.3.dev18}/tests_base_user/settings/__init__.py +0 -0
  241. {django_pfx-1.7.3.dev14 → django_pfx-1.7.3.dev18}/tests_base_user/settings/ci.py +0 -0
  242. {django_pfx-1.7.3.dev14 → django_pfx-1.7.3.dev18}/tests_base_user/settings/common.py +0 -0
  243. {django_pfx-1.7.3.dev14 → django_pfx-1.7.3.dev18}/tests_base_user/settings/dev.py +0 -0
  244. {django_pfx-1.7.3.dev14 → django_pfx-1.7.3.dev18}/tests_base_user/settings/dev_custom_example.py +0 -0
  245. {django_pfx-1.7.3.dev14 → django_pfx-1.7.3.dev18}/tests_base_user/settings/dev_default.py +0 -0
  246. {django_pfx-1.7.3.dev14 → django_pfx-1.7.3.dev18}/tests_base_user/tests/__init__.py +0 -0
  247. {django_pfx-1.7.3.dev14 → django_pfx-1.7.3.dev18}/tests_base_user/tests/test_api.py +0 -0
  248. {django_pfx-1.7.3.dev14 → django_pfx-1.7.3.dev18}/tests_base_user/tests/test_auth_api.py +0 -0
  249. {django_pfx-1.7.3.dev14 → django_pfx-1.7.3.dev18}/tests_base_user/urls.py +0 -0
  250. {django_pfx-1.7.3.dev14 → django_pfx-1.7.3.dev18}/tests_base_user/views.py +0 -0
  251. {django_pfx-1.7.3.dev14 → django_pfx-1.7.3.dev18}/tests_custom_user/__init__.py +0 -0
  252. {django_pfx-1.7.3.dev14 → django_pfx-1.7.3.dev18}/tests_custom_user/migrations/0001_initial.py +0 -0
  253. {django_pfx-1.7.3.dev14 → django_pfx-1.7.3.dev18}/tests_custom_user/migrations/__init__.py +0 -0
  254. {django_pfx-1.7.3.dev14 → django_pfx-1.7.3.dev18}/tests_custom_user/models.py +0 -0
  255. {django_pfx-1.7.3.dev14 → django_pfx-1.7.3.dev18}/tests_custom_user/settings/__init__.py +0 -0
  256. {django_pfx-1.7.3.dev14 → django_pfx-1.7.3.dev18}/tests_custom_user/settings/ci.py +0 -0
  257. {django_pfx-1.7.3.dev14 → django_pfx-1.7.3.dev18}/tests_custom_user/settings/common.py +0 -0
  258. {django_pfx-1.7.3.dev14 → django_pfx-1.7.3.dev18}/tests_custom_user/settings/dev.py +0 -0
  259. {django_pfx-1.7.3.dev14 → django_pfx-1.7.3.dev18}/tests_custom_user/settings/dev_custom_example.py +0 -0
  260. {django_pfx-1.7.3.dev14 → django_pfx-1.7.3.dev18}/tests_custom_user/settings/dev_default.py +0 -0
  261. {django_pfx-1.7.3.dev14 → django_pfx-1.7.3.dev18}/tests_custom_user/tests/__init__.py +0 -0
  262. {django_pfx-1.7.3.dev14 → django_pfx-1.7.3.dev18}/tests_custom_user/tests/test_api.py +0 -0
  263. {django_pfx-1.7.3.dev14 → django_pfx-1.7.3.dev18}/tests_custom_user/tests/test_auth_api.py +0 -0
  264. {django_pfx-1.7.3.dev14 → django_pfx-1.7.3.dev18}/tests_custom_user/urls.py +0 -0
  265. {django_pfx-1.7.3.dev14 → django_pfx-1.7.3.dev18}/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.dev14
3
+ Version: 1.7.3.dev18
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.dev14
3
+ Version: 1.7.3.dev18
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
@@ -119,9 +119,9 @@ class MyUser(MFAUserMixin, AbstractPFXBaseUser):
119
119
  pass
120
120
  ```
121
121
 
122
- :::{note}
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 enable or disable the authenticator OTP using the [services documented below](#enable-mfa-otp).
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
- ### Enable MFA OTP
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
- **Request :** `GET` `/auth/otp/setup-uri`
286
+ All three MFA methods follow the same `{setup, confirm, disable}` pattern under `/auth/mfa/{authenticator|email|sms}/`.
293
287
 
294
- **Responses :**
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
- * `HTTP 400` if the otp is already enabled
297
- * `HTTP 200` with the following body
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
- | Field | Description |
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
- **Request :** `PUT` `/auth/otp/enable`
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 | Description |
308
- |-------------|---------------------------------------|
309
- | otp_code | the otp code retrieved in the OTP app |
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
- * `HTTP 422` if the otp code is invalid
314
- * `HTTP 200` if the otp code is valid
360
+ ---
315
361
 
316
- ### Disable MFA OTP
317
- A service to disable the MFA with OTP.
362
+ **Request:** `PUT` `/auth/mfa/authenticator/disable`
318
363
 
319
- **Request :** `PUT` `/auth/otp/disable`
364
+ Disables authenticator MFA. Requires a valid OTP code (TOTP or HOTP).
320
365
 
321
366
  **Request body:**
322
367
 
323
- | Field | Description |
324
- |-------------|---------------------------------------|
325
- | otp_code | the otp code retrieved in the OTP app |
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 otp code is invalid
330
- * `HTTP 200` if the otp code is valid
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
@@ -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-05-21 16:23+0200\n"
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
- "Notre équipe d’assistance en a été informée."
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:386
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:397
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:460 views/authentication_views.py:628
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:465
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:468 views/authentication_views.py:637
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:557
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:559
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:671
305
- msgid "OTP is already enabled"
306
- msgstr "OTP est déjà activé"
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:712 views/authentication_views.py:748
313
- msgid "Invalid code"
314
- msgstr "Code invalide"
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:747
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:1082
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é"
@@ -0,0 +1,35 @@
1
+ import logging
2
+
3
+ from django.contrib.auth.models import AnonymousUser
4
+ from django.http import parse_cookie
5
+
6
+ from channels.db import database_sync_to_async
7
+ from channels.middleware import BaseMiddleware
8
+
9
+ from pfx.pfxcore.middleware.authentication import JWTTokenDecodeMixin
10
+
11
+ logger = logging.getLogger(__name__)
12
+
13
+
14
+ class WSCookieAuthenticationMiddleware(JWTTokenDecodeMixin, BaseMiddleware):
15
+ async def __call__(self, scope, receive, send):
16
+ if scope.get("user") is not None:
17
+ return await super().__call__(scope, receive, send)
18
+
19
+ headers = dict(scope.get("headers", []))
20
+ cookies = {}
21
+ if b"cookie" in headers:
22
+ cookies = parse_cookie(headers[b"cookie"].decode())
23
+
24
+ key = cookies.get("token")
25
+ if key:
26
+ try:
27
+ user, __, __ = await database_sync_to_async(
28
+ self.decode_jwt)(key)
29
+ except Exception as e:
30
+ logger.exception(e, exc_info=True)
31
+ user = AnonymousUser()
32
+ else:
33
+ user = AnonymousUser()
34
+ scope['user'] = user
35
+ 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
- """Activate OTP for this user.
141
+ """Start authenticator setup.
87
142
 
88
- Generates a new temporary OTP secret token. To complete activation,
89
- call `confirm_otp` with a valid code.
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 OTP activation for this user.
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 OTP code for the temporary OTP secret key.
102
- :returns: `True` if success, `False` otherwise.
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 OTP for this user.
167
+ """Disable authenticator MFA.
116
168
 
117
- Remove the OTP secret token.
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
- 'otp_secret_token', 'mfa_authenticator_enabled'])
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.