django-pfx 1.4.dev46__tar.gz → 1.4.dev50__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 (142) hide show
  1. {django_pfx-1.4.dev46 → django_pfx-1.4.dev50}/PKG-INFO +1 -1
  2. {django_pfx-1.4.dev46 → django_pfx-1.4.dev50}/django_pfx.egg-info/PKG-INFO +1 -1
  3. {django_pfx-1.4.dev46 → django_pfx-1.4.dev50}/doc/source/authentication.md +4 -0
  4. {django_pfx-1.4.dev46 → django_pfx-1.4.dev50}/pfx/pfxcore/default_settings.py +2 -0
  5. {django_pfx-1.4.dev46 → django_pfx-1.4.dev50}/pfx/pfxcore/models/otp_user_mixin.py +14 -4
  6. {django_pfx-1.4.dev46 → django_pfx-1.4.dev50}/tests/tests/test_auth_api.py +50 -1
  7. {django_pfx-1.4.dev46 → django_pfx-1.4.dev50}/.gitignore +0 -0
  8. {django_pfx-1.4.dev46 → django_pfx-1.4.dev50}/.gitlab-ci.yml +0 -0
  9. {django_pfx-1.4.dev46 → django_pfx-1.4.dev50}/.pre-commit-config.yaml +0 -0
  10. {django_pfx-1.4.dev46 → django_pfx-1.4.dev50}/LICENSE +0 -0
  11. {django_pfx-1.4.dev46 → django_pfx-1.4.dev50}/MANIFEST.in +0 -0
  12. {django_pfx-1.4.dev46 → django_pfx-1.4.dev50}/README.md +0 -0
  13. {django_pfx-1.4.dev46 → django_pfx-1.4.dev50}/django_pfx.egg-info/SOURCES.txt +0 -0
  14. {django_pfx-1.4.dev46 → django_pfx-1.4.dev50}/django_pfx.egg-info/dependency_links.txt +0 -0
  15. {django_pfx-1.4.dev46 → django_pfx-1.4.dev50}/django_pfx.egg-info/requires.txt +0 -0
  16. {django_pfx-1.4.dev46 → django_pfx-1.4.dev50}/django_pfx.egg-info/top_level.txt +0 -0
  17. {django_pfx-1.4.dev46 → django_pfx-1.4.dev50}/doc/Makefile +0 -0
  18. {django_pfx-1.4.dev46 → django_pfx-1.4.dev50}/doc/conf.py +0 -0
  19. {django_pfx-1.4.dev46 → django_pfx-1.4.dev50}/doc/index.rst +0 -0
  20. {django_pfx-1.4.dev46 → django_pfx-1.4.dev50}/doc/source/api.views.rst +0 -0
  21. {django_pfx-1.4.dev46 → django_pfx-1.4.dev50}/doc/source/decorator.md +0 -0
  22. {django_pfx-1.4.dev46 → django_pfx-1.4.dev50}/doc/source/generate_openapi.md +0 -0
  23. {django_pfx-1.4.dev46 → django_pfx-1.4.dev50}/doc/source/getting_started.md +0 -0
  24. {django_pfx-1.4.dev46 → django_pfx-1.4.dev50}/doc/source/internationalisation.md +0 -0
  25. {django_pfx-1.4.dev46 → django_pfx-1.4.dev50}/doc/source/model.md +0 -0
  26. {django_pfx-1.4.dev46 → django_pfx-1.4.dev50}/doc/source/pfx_views.md +0 -0
  27. {django_pfx-1.4.dev46 → django_pfx-1.4.dev50}/doc/source/profiling.md +0 -0
  28. {django_pfx-1.4.dev46 → django_pfx-1.4.dev50}/doc/source/settings.md +0 -0
  29. {django_pfx-1.4.dev46 → django_pfx-1.4.dev50}/doc/source/testing.md +0 -0
  30. {django_pfx-1.4.dev46 → django_pfx-1.4.dev50}/img/pfx.png +0 -0
  31. {django_pfx-1.4.dev46 → django_pfx-1.4.dev50}/img/pfx.svg +0 -0
  32. {django_pfx-1.4.dev46 → django_pfx-1.4.dev50}/make_messages +0 -0
  33. {django_pfx-1.4.dev46 → django_pfx-1.4.dev50}/manage.py +0 -0
  34. {django_pfx-1.4.dev46 → django_pfx-1.4.dev50}/pfx/__init__.py +0 -0
  35. {django_pfx-1.4.dev46 → django_pfx-1.4.dev50}/pfx/pfxcore/__init__.py +0 -0
  36. {django_pfx-1.4.dev46 → django_pfx-1.4.dev50}/pfx/pfxcore/apidoc/__init__.py +0 -0
  37. {django_pfx-1.4.dev46 → django_pfx-1.4.dev50}/pfx/pfxcore/apidoc/parameters.py +0 -0
  38. {django_pfx-1.4.dev46 → django_pfx-1.4.dev50}/pfx/pfxcore/apidoc/schema.py +0 -0
  39. {django_pfx-1.4.dev46 → django_pfx-1.4.dev50}/pfx/pfxcore/apidoc/tags.py +0 -0
  40. {django_pfx-1.4.dev46 → django_pfx-1.4.dev50}/pfx/pfxcore/apps.py +0 -0
  41. {django_pfx-1.4.dev46 → django_pfx-1.4.dev50}/pfx/pfxcore/decorator/__init__.py +0 -0
  42. {django_pfx-1.4.dev46 → django_pfx-1.4.dev50}/pfx/pfxcore/decorator/rest.py +0 -0
  43. {django_pfx-1.4.dev46 → django_pfx-1.4.dev50}/pfx/pfxcore/exceptions.py +0 -0
  44. {django_pfx-1.4.dev46 → django_pfx-1.4.dev50}/pfx/pfxcore/fields.py +0 -0
  45. {django_pfx-1.4.dev46 → django_pfx-1.4.dev50}/pfx/pfxcore/http/__init__.py +0 -0
  46. {django_pfx-1.4.dev46 → django_pfx-1.4.dev50}/pfx/pfxcore/http/json_response.py +0 -0
  47. {django_pfx-1.4.dev46 → django_pfx-1.4.dev50}/pfx/pfxcore/locale/fr/LC_MESSAGES/django.mo +0 -0
  48. {django_pfx-1.4.dev46 → django_pfx-1.4.dev50}/pfx/pfxcore/locale/fr/LC_MESSAGES/django.po +0 -0
  49. {django_pfx-1.4.dev46 → django_pfx-1.4.dev50}/pfx/pfxcore/management/__init__.py +0 -0
  50. {django_pfx-1.4.dev46 → django_pfx-1.4.dev50}/pfx/pfxcore/management/commands/__init__.py +0 -0
  51. {django_pfx-1.4.dev46 → django_pfx-1.4.dev50}/pfx/pfxcore/management/commands/makeapidoc.py +0 -0
  52. {django_pfx-1.4.dev46 → django_pfx-1.4.dev50}/pfx/pfxcore/management/commands/profile.py +0 -0
  53. {django_pfx-1.4.dev46 → django_pfx-1.4.dev50}/pfx/pfxcore/middleware/__init__.py +0 -0
  54. {django_pfx-1.4.dev46 → django_pfx-1.4.dev50}/pfx/pfxcore/middleware/authentication.py +0 -0
  55. {django_pfx-1.4.dev46 → django_pfx-1.4.dev50}/pfx/pfxcore/middleware/locale.py +0 -0
  56. {django_pfx-1.4.dev46 → django_pfx-1.4.dev50}/pfx/pfxcore/middleware/profiling.py +0 -0
  57. {django_pfx-1.4.dev46 → django_pfx-1.4.dev50}/pfx/pfxcore/migrations/0001_initial.py +0 -0
  58. {django_pfx-1.4.dev46 → django_pfx-1.4.dev50}/pfx/pfxcore/migrations/__init__.py +0 -0
  59. {django_pfx-1.4.dev46 → django_pfx-1.4.dev50}/pfx/pfxcore/models/__init__.py +0 -0
  60. {django_pfx-1.4.dev46 → django_pfx-1.4.dev50}/pfx/pfxcore/models/abstract_pfx_base_user.py +0 -0
  61. {django_pfx-1.4.dev46 → django_pfx-1.4.dev50}/pfx/pfxcore/models/cache_mixins.py +0 -0
  62. {django_pfx-1.4.dev46 → django_pfx-1.4.dev50}/pfx/pfxcore/models/login_ban.py +0 -0
  63. {django_pfx-1.4.dev46 → django_pfx-1.4.dev50}/pfx/pfxcore/models/not_null_fields.py +0 -0
  64. {django_pfx-1.4.dev46 → django_pfx-1.4.dev50}/pfx/pfxcore/models/pfx_models.py +0 -0
  65. {django_pfx-1.4.dev46 → django_pfx-1.4.dev50}/pfx/pfxcore/models/pfx_user.py +0 -0
  66. {django_pfx-1.4.dev46 → django_pfx-1.4.dev50}/pfx/pfxcore/models/user_filtered_queryset_mixin.py +0 -0
  67. {django_pfx-1.4.dev46 → django_pfx-1.4.dev50}/pfx/pfxcore/serializers/__init__.py +0 -0
  68. {django_pfx-1.4.dev46 → django_pfx-1.4.dev50}/pfx/pfxcore/serializers/json.py +0 -0
  69. {django_pfx-1.4.dev46 → django_pfx-1.4.dev50}/pfx/pfxcore/settings.py +0 -0
  70. {django_pfx-1.4.dev46 → django_pfx-1.4.dev50}/pfx/pfxcore/shortcuts.py +0 -0
  71. {django_pfx-1.4.dev46 → django_pfx-1.4.dev50}/pfx/pfxcore/storage/__init__.py +0 -0
  72. {django_pfx-1.4.dev46 → django_pfx-1.4.dev50}/pfx/pfxcore/storage/s3_storage.py +0 -0
  73. {django_pfx-1.4.dev46 → django_pfx-1.4.dev50}/pfx/pfxcore/templates/registration/otp_code_email.txt +0 -0
  74. {django_pfx-1.4.dev46 → django_pfx-1.4.dev50}/pfx/pfxcore/templates/registration/otp_code_subject.txt +0 -0
  75. {django_pfx-1.4.dev46 → django_pfx-1.4.dev50}/pfx/pfxcore/templates/registration/password_reset_email.txt +0 -0
  76. {django_pfx-1.4.dev46 → django_pfx-1.4.dev50}/pfx/pfxcore/templates/registration/password_reset_subject.txt +0 -0
  77. {django_pfx-1.4.dev46 → django_pfx-1.4.dev50}/pfx/pfxcore/templates/registration/welcome_email.txt +0 -0
  78. {django_pfx-1.4.dev46 → django_pfx-1.4.dev50}/pfx/pfxcore/templates/registration/welcome_subject.txt +0 -0
  79. {django_pfx-1.4.dev46 → django_pfx-1.4.dev50}/pfx/pfxcore/test.py +0 -0
  80. {django_pfx-1.4.dev46 → django_pfx-1.4.dev50}/pfx/pfxcore/urls.py +0 -0
  81. {django_pfx-1.4.dev46 → django_pfx-1.4.dev50}/pfx/pfxcore/views/__init__.py +0 -0
  82. {django_pfx-1.4.dev46 → django_pfx-1.4.dev50}/pfx/pfxcore/views/authentication_views.py +0 -0
  83. {django_pfx-1.4.dev46 → django_pfx-1.4.dev50}/pfx/pfxcore/views/fields.py +0 -0
  84. {django_pfx-1.4.dev46 → django_pfx-1.4.dev50}/pfx/pfxcore/views/filters_views.py +0 -0
  85. {django_pfx-1.4.dev46 → django_pfx-1.4.dev50}/pfx/pfxcore/views/locale_views.py +0 -0
  86. {django_pfx-1.4.dev46 → django_pfx-1.4.dev50}/pfx/pfxcore/views/parameters/__init__.py +0 -0
  87. {django_pfx-1.4.dev46 → django_pfx-1.4.dev50}/pfx/pfxcore/views/parameters/date_format.py +0 -0
  88. {django_pfx-1.4.dev46 → django_pfx-1.4.dev50}/pfx/pfxcore/views/parameters/groups.py +0 -0
  89. {django_pfx-1.4.dev46 → django_pfx-1.4.dev50}/pfx/pfxcore/views/parameters/list_count.py +0 -0
  90. {django_pfx-1.4.dev46 → django_pfx-1.4.dev50}/pfx/pfxcore/views/parameters/list_items.py +0 -0
  91. {django_pfx-1.4.dev46 → django_pfx-1.4.dev50}/pfx/pfxcore/views/parameters/list_mode.py +0 -0
  92. {django_pfx-1.4.dev46 → django_pfx-1.4.dev50}/pfx/pfxcore/views/parameters/list_order.py +0 -0
  93. {django_pfx-1.4.dev46 → django_pfx-1.4.dev50}/pfx/pfxcore/views/parameters/list_search.py +0 -0
  94. {django_pfx-1.4.dev46 → django_pfx-1.4.dev50}/pfx/pfxcore/views/parameters/media_redirect.py +0 -0
  95. {django_pfx-1.4.dev46 → django_pfx-1.4.dev50}/pfx/pfxcore/views/parameters/meta_fields.py +0 -0
  96. {django_pfx-1.4.dev46 → django_pfx-1.4.dev50}/pfx/pfxcore/views/parameters/meta_filters.py +0 -0
  97. {django_pfx-1.4.dev46 → django_pfx-1.4.dev50}/pfx/pfxcore/views/parameters/meta_orders.py +0 -0
  98. {django_pfx-1.4.dev46 → django_pfx-1.4.dev50}/pfx/pfxcore/views/parameters/subset.py +0 -0
  99. {django_pfx-1.4.dev46 → django_pfx-1.4.dev50}/pfx/pfxcore/views/parameters/subset_limit.py +0 -0
  100. {django_pfx-1.4.dev46 → django_pfx-1.4.dev50}/pfx/pfxcore/views/parameters/subset_offset.py +0 -0
  101. {django_pfx-1.4.dev46 → django_pfx-1.4.dev50}/pfx/pfxcore/views/parameters/subset_page.py +0 -0
  102. {django_pfx-1.4.dev46 → django_pfx-1.4.dev50}/pfx/pfxcore/views/parameters/subset_page_size.py +0 -0
  103. {django_pfx-1.4.dev46 → django_pfx-1.4.dev50}/pfx/pfxcore/views/parameters/subset_page_subset.py +0 -0
  104. {django_pfx-1.4.dev46 → django_pfx-1.4.dev50}/pfx/pfxcore/views/rest_views.py +0 -0
  105. {django_pfx-1.4.dev46 → django_pfx-1.4.dev50}/pfx/settings/__init__.py +0 -0
  106. {django_pfx-1.4.dev46 → django_pfx-1.4.dev50}/pfx/settings/dev.py +0 -0
  107. {django_pfx-1.4.dev46 → django_pfx-1.4.dev50}/pyproject.toml +0 -0
  108. {django_pfx-1.4.dev46 → django_pfx-1.4.dev50}/requirements.txt +0 -0
  109. {django_pfx-1.4.dev46 → django_pfx-1.4.dev50}/serve-doc +0 -0
  110. {django_pfx-1.4.dev46 → django_pfx-1.4.dev50}/setup.cfg +0 -0
  111. {django_pfx-1.4.dev46 → django_pfx-1.4.dev50}/setup.py +0 -0
  112. {django_pfx-1.4.dev46 → django_pfx-1.4.dev50}/tests/__init__.py +0 -0
  113. {django_pfx-1.4.dev46 → django_pfx-1.4.dev50}/tests/locale/fr/LC_MESSAGES/django.po +0 -0
  114. {django_pfx-1.4.dev46 → django_pfx-1.4.dev50}/tests/models.py +0 -0
  115. {django_pfx-1.4.dev46 → django_pfx-1.4.dev50}/tests/settings/__init__.py +0 -0
  116. {django_pfx-1.4.dev46 → django_pfx-1.4.dev50}/tests/settings/ci.py +0 -0
  117. {django_pfx-1.4.dev46 → django_pfx-1.4.dev50}/tests/settings/common.py +0 -0
  118. {django_pfx-1.4.dev46 → django_pfx-1.4.dev50}/tests/settings/dev.py +0 -0
  119. {django_pfx-1.4.dev46 → django_pfx-1.4.dev50}/tests/settings/dev_custom_example.py +0 -0
  120. {django_pfx-1.4.dev46 → django_pfx-1.4.dev50}/tests/settings/dev_default.py +0 -0
  121. {django_pfx-1.4.dev46 → django_pfx-1.4.dev50}/tests/tests/__init__.py +0 -0
  122. {django_pfx-1.4.dev46 → django_pfx-1.4.dev50}/tests/tests/basic_api_errors.py +0 -0
  123. {django_pfx-1.4.dev46 → django_pfx-1.4.dev50}/tests/tests/basic_api_test.py +0 -0
  124. {django_pfx-1.4.dev46 → django_pfx-1.4.dev50}/tests/tests/test_api_doc.py +0 -0
  125. {django_pfx-1.4.dev46 → django_pfx-1.4.dev50}/tests/tests/test_body_mixin.py +0 -0
  126. {django_pfx-1.4.dev46 → django_pfx-1.4.dev50}/tests/tests/test_cache.py +0 -0
  127. {django_pfx-1.4.dev46 → django_pfx-1.4.dev50}/tests/tests/test_client.py +0 -0
  128. {django_pfx-1.4.dev46 → django_pfx-1.4.dev50}/tests/tests/test_fields.py +0 -0
  129. {django_pfx-1.4.dev46 → django_pfx-1.4.dev50}/tests/tests/test_filters.py +0 -0
  130. {django_pfx-1.4.dev46 → django_pfx-1.4.dev50}/tests/tests/test_locale_api.py +0 -0
  131. {django_pfx-1.4.dev46 → django_pfx-1.4.dev50}/tests/tests/test_perm_tests.py +0 -0
  132. {django_pfx-1.4.dev46 → django_pfx-1.4.dev50}/tests/tests/test_perms_api.py +0 -0
  133. {django_pfx-1.4.dev46 → django_pfx-1.4.dev50}/tests/tests/test_profiling_middleware.py +0 -0
  134. {django_pfx-1.4.dev46 → django_pfx-1.4.dev50}/tests/tests/test_settings.py +0 -0
  135. {django_pfx-1.4.dev46 → django_pfx-1.4.dev50}/tests/tests/test_shortcuts.py +0 -0
  136. {django_pfx-1.4.dev46 → django_pfx-1.4.dev50}/tests/tests/test_timezone_middleware.py +0 -0
  137. {django_pfx-1.4.dev46 → django_pfx-1.4.dev50}/tests/tests/test_tools.py +0 -0
  138. {django_pfx-1.4.dev46 → django_pfx-1.4.dev50}/tests/tests/test_user_queryset.py +0 -0
  139. {django_pfx-1.4.dev46 → django_pfx-1.4.dev50}/tests/tests/test_view_decorators.py +0 -0
  140. {django_pfx-1.4.dev46 → django_pfx-1.4.dev50}/tests/tests/test_view_fields.py +0 -0
  141. {django_pfx-1.4.dev46 → django_pfx-1.4.dev50}/tests/urls.py +0 -0
  142. {django_pfx-1.4.dev46 → django_pfx-1.4.dev50}/tests/views.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: django-pfx
3
- Version: 1.4.dev46
3
+ Version: 1.4.dev50
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.1
2
2
  Name: django-pfx
3
- Version: 1.4.dev46
3
+ Version: 1.4.dev50
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
@@ -97,6 +97,10 @@ class MyUser(OtpUserMixin, AbstractPFXBaseUser):
97
97
  * `PFX_TOKEN_OTP_VALIDITY`: Validity for OTP tokens (corresponds to the maximum time to enter
98
98
  an OTP code after logging in with a password) (optional, default `{'minutes': 15}`)
99
99
  * `PFX_HOTP_CODE_VALIDITY`: Validity of HOTP codes in minutes (used to send code by email) (optional, default `15`).
100
+ * `PFX_OTP_VALID_WINDOW`: TOTP valid window (optional, default `1`).
101
+ According to [RFC 6238 section 5.2](https://www.ietf.org/rfc/rfc6238.html#section-5.2).
102
+ * `PFX_OTP_IMAGE`: An image https URL used by FreeOTP. See [FreeOTP URI](https://github.com/npmccallum/freeotp-android/blob/master/URI.md).
103
+ * `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).
100
104
 
101
105
  The user can then enable or disable the OTP auth using the [services documented below](#enable-mfa-otp).
102
106
 
@@ -2,7 +2,9 @@ PFX_TOKEN_SHORT_VALIDITY = {'hours': 12}
2
2
  PFX_TOKEN_LONG_VALIDITY = {'days': 30}
3
3
  PFX_TOKEN_OTP_VALIDITY = {'minutes': 15}
4
4
  PFX_HOTP_CODE_VALIDITY = 15
5
+ PFX_OTP_VALID_WINDOW = 1
5
6
  PFX_OTP_IMAGE = None
7
+ PFX_OTP_COLOR = None
6
8
 
7
9
  PFX_COOKIE_DOMAIN = None
8
10
  PFX_COOKIE_SECURE = True
@@ -65,7 +65,7 @@ class OtpUserMixin(models.Model):
65
65
  self.otp_secret_token = None
66
66
  self.save(update_fields=['otp_secret_token'])
67
67
 
68
- def get_otp_setup_uri(self, tmp=False):
68
+ def get_otp_setup_uri(self, tmp=False, with_color=True):
69
69
  """Return the setup URL for OTP activation.
70
70
  """
71
71
  import pyotp
@@ -73,9 +73,14 @@ class OtpUserMixin(models.Model):
73
73
  name=self.get_username(), issuer_name=settings.PFX_SITE_NAME)
74
74
  if settings.PFX_OTP_IMAGE:
75
75
  args['image'] = settings.PFX_OTP_IMAGE
76
- return pyotp.totp.TOTP(
76
+ uri = pyotp.totp.TOTP(
77
77
  tmp and self.otp_secret_token_tmp or
78
78
  self.otp_secret_token).provisioning_uri(**args)
79
+ if with_color and settings.PFX_OTP_COLOR:
80
+ # TODO: Can be put in provisioning_uri args if
81
+ # https://github.com/pyauth/pyotp/pull/164 is merge and published.
82
+ uri = f"{uri}&color={settings.PFX_OTP_COLOR}"
83
+ return uri
79
84
 
80
85
  def is_otp_valid(self, otp_code, tmp=False):
81
86
  """Verify an OTP code.
@@ -86,8 +91,13 @@ class OtpUserMixin(models.Model):
86
91
  :returns: `True` if the code is valid, `False` otherwise.
87
92
  """
88
93
  import pyotp
89
- totp = pyotp.parse_uri(self.get_otp_setup_uri(tmp=tmp))
90
- valid = totp.verify(otp_code)
94
+
95
+ # TODO : The with_color paramter can be removed if
96
+ # https://github.com/pyauth/pyotp/pull/164 is merge and published.
97
+ totp = pyotp.parse_uri(
98
+ self.get_otp_setup_uri(tmp=tmp, with_color=False))
99
+ valid = totp.verify(
100
+ otp_code, valid_window=settings.PFX_OTP_VALID_WINDOW)
91
101
  if not valid and timezone.now() <= self.hotp_expiry:
92
102
  hotp = pyotp.hotp.HOTP(
93
103
  tmp and self.otp_secret_token_tmp or
@@ -8,6 +8,7 @@ from django.conf import settings
8
8
  from django.contrib.auth.tokens import default_token_generator
9
9
  from django.core import mail
10
10
  from django.test import TransactionTestCase, modify_settings, override_settings
11
+ from django.utils import timezone
11
12
  from django.utils.encoding import force_bytes
12
13
  from django.utils.http import urlsafe_base64_encode
13
14
 
@@ -894,7 +895,9 @@ class AuthAPITest(TestAssertMixin, TransactionTestCase):
894
895
  self.assertRC(response, 200)
895
896
  self.client.token = self.get_val(response, 'token')
896
897
 
897
- @override_settings(PFX_OTP_IMAGE="https://example.org/fake.png")
898
+ @override_settings(
899
+ PFX_OTP_IMAGE="https://example.org/fake.png",
900
+ PFX_OTP_COLOR="FF0000")
898
901
  def test_otp_enable(self):
899
902
  self.client.login(username='jrr.tolkien', password='RIGHT PASSWORD')
900
903
 
@@ -904,6 +907,7 @@ class AuthAPITest(TestAssertMixin, TransactionTestCase):
904
907
  self.assertJIn(response, 'setup_uri', "otpauth://totp/")
905
908
  self.assertJIn(response, 'setup_uri', "Books%20Demo:jrr.tolkien")
906
909
  self.assertJIn(response, 'setup_uri', "issuer=Books%20Demo")
910
+ self.assertJIn(response, 'setup_uri', "color=FF0000")
907
911
  self.assertJIn(
908
912
  response, 'setup_uri',
909
913
  "image=https%3A%2F%2Fexample.org%2Ffake.png")
@@ -1088,6 +1092,51 @@ class AuthAPITest(TestAssertMixin, TransactionTestCase):
1088
1092
  datetime.fromtimestamp(decoded['exp']),
1089
1093
  datetime(2023, 5, 2, 8, 10))
1090
1094
 
1095
+ @override_settings(
1096
+ PFX_OTP_VALID_WINDOW=0,
1097
+ PFX_TOKEN_SHORT_VALIDITY={'minutes': 30})
1098
+ def test_otp_login_valid_window_0(self):
1099
+ totp = self.enable_otp(self.user1)
1100
+
1101
+ with freeze_time("2023-05-01 08:00:00"):
1102
+ response = self.client.post('/api/auth/login', dict(
1103
+ username='jrr.tolkien',
1104
+ password='RIGHT PASSWORD'))
1105
+ self.assertRC(response, 200)
1106
+ token = self.get_val(response, 'token')
1107
+
1108
+ response = self.client.post('/api/auth/otp/login', dict(
1109
+ token=token, otp_code=totp.at(
1110
+ timezone.now() - timedelta(seconds=1))))
1111
+ self.assertRC(response, 422)
1112
+
1113
+ response = self.client.post('/api/auth/otp/login', dict(
1114
+ token=token, otp_code=totp.at(timezone.now())))
1115
+ self.assertRC(response, 200)
1116
+
1117
+ @override_settings(
1118
+ PFX_OTP_VALID_WINDOW=1,
1119
+ PFX_TOKEN_SHORT_VALIDITY={'minutes': 30})
1120
+ def test_otp_login_valid_window_1(self):
1121
+ totp = self.enable_otp(self.user1)
1122
+
1123
+ with freeze_time("2023-05-01 08:00:00"):
1124
+ response = self.client.post('/api/auth/login', dict(
1125
+ username='jrr.tolkien',
1126
+ password='RIGHT PASSWORD'))
1127
+ self.assertRC(response, 200)
1128
+ token = self.get_val(response, 'token')
1129
+
1130
+ response = self.client.post('/api/auth/otp/login', dict(
1131
+ token=token, otp_code=totp.at(
1132
+ timezone.now() - timedelta(seconds=31))))
1133
+ self.assertRC(response, 422)
1134
+
1135
+ response = self.client.post('/api/auth/otp/login', dict(
1136
+ token=token, otp_code=totp.at(
1137
+ timezone.now() - timedelta(seconds=30))))
1138
+ self.assertRC(response, 200)
1139
+
1091
1140
  @override_settings(
1092
1141
  PFX_TOKEN_SHORT_VALIDITY={'minutes': 30},
1093
1142
  PFX_LOGIN_BAN_FAILED_NUMBER=2,
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes