oxutils 0.1.22__tar.gz → 0.3.2__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.
- {oxutils-0.1.22 → oxutils-0.3.2}/PKG-INFO +6 -3
- {oxutils-0.1.22 → oxutils-0.3.2}/pyproject.toml +14 -4
- oxutils-0.3.2/src/oxutils/__init__.py +28 -0
- oxutils-0.3.2/src/oxutils/auth/adapter.py +212 -0
- oxutils-0.3.2/src/oxutils/auth/app_settings.py +25 -0
- oxutils-0.3.2/src/oxutils/auth/apps.py +6 -0
- oxutils-0.3.2/src/oxutils/auth/constants.py +14 -0
- oxutils-0.3.2/src/oxutils/auth/controllers.py +203 -0
- oxutils-0.3.2/src/oxutils/auth/emails/controllers.py +167 -0
- oxutils-0.3.2/src/oxutils/auth/emails/schemas.py +82 -0
- oxutils-0.3.2/src/oxutils/auth/emails/services.py +51 -0
- oxutils-0.3.2/src/oxutils/auth/exceptions.py +29 -0
- oxutils-0.3.2/src/oxutils/auth/invitations/backend.py +229 -0
- oxutils-0.3.2/src/oxutils/auth/invitations/controllers.py +248 -0
- oxutils-0.3.2/src/oxutils/auth/invitations/models.py +134 -0
- oxutils-0.3.2/src/oxutils/auth/invitations/schemas.py +92 -0
- oxutils-0.3.2/src/oxutils/auth/invitations/tokens.py +34 -0
- oxutils-0.3.2/src/oxutils/auth/mfa/__init__.py +23 -0
- oxutils-0.3.2/src/oxutils/auth/mfa/adapter.py +5 -0
- oxutils-0.3.2/src/oxutils/auth/mfa/base/controllers.py +67 -0
- oxutils-0.3.2/src/oxutils/auth/mfa/base/schemas.py +66 -0
- oxutils-0.3.2/src/oxutils/auth/mfa/recovery_codes/controllers.py +40 -0
- oxutils-0.3.2/src/oxutils/auth/mfa/recovery_codes/flows.py +76 -0
- oxutils-0.3.2/src/oxutils/auth/mfa/recovery_codes/schemas.py +28 -0
- oxutils-0.3.2/src/oxutils/auth/mfa/recovery_codes/services.py +80 -0
- oxutils-0.3.2/src/oxutils/auth/mfa/totp/controllers.py +50 -0
- oxutils-0.3.2/src/oxutils/auth/mfa/totp/flows.py +35 -0
- oxutils-0.3.2/src/oxutils/auth/mfa/totp/permissions.py +23 -0
- oxutils-0.3.2/src/oxutils/auth/mfa/totp/schemas.py +51 -0
- oxutils-0.3.2/src/oxutils/auth/mfa/totp/services.py +135 -0
- oxutils-0.3.2/src/oxutils/auth/mfa/utils.py +29 -0
- oxutils-0.3.2/src/oxutils/auth/migrations/0001_initial.py +97 -0
- oxutils-0.3.2/src/oxutils/auth/mixins.py +68 -0
- oxutils-0.3.2/src/oxutils/auth/models.py +24 -0
- oxutils-0.3.2/src/oxutils/auth/password_reset/controllers.py +167 -0
- oxutils-0.3.2/src/oxutils/auth/password_reset/models.py +12 -0
- oxutils-0.3.2/src/oxutils/auth/password_reset/permissions.py +31 -0
- oxutils-0.3.2/src/oxutils/auth/password_reset/schemas.py +83 -0
- oxutils-0.3.2/src/oxutils/auth/registration/controllers.py +92 -0
- oxutils-0.3.2/src/oxutils/auth/registration/schemas.py +173 -0
- oxutils-0.3.2/src/oxutils/auth/schemas.py +315 -0
- oxutils-0.3.2/src/oxutils/auth/sessions/controllers.py +134 -0
- oxutils-0.3.2/src/oxutils/auth/sessions/schemas.py +18 -0
- oxutils-0.3.2/src/oxutils/auth/signals.py +91 -0
- oxutils-0.3.2/src/oxutils/auth/templatetags/spa_urls.py +17 -0
- oxutils-0.3.2/src/oxutils/auth/tokens/app_settings.py +6 -0
- oxutils-0.3.2/src/oxutils/auth/tokens/models.py +159 -0
- oxutils-0.3.2/src/oxutils/auth/tokens/schemas.py +26 -0
- oxutils-0.3.2/src/oxutils/auth/tokens/tokens.py +104 -0
- oxutils-0.3.2/src/oxutils/auth/tokens/utils.py +53 -0
- oxutils-0.3.2/src/oxutils/auth/utils.py +381 -0
- oxutils-0.3.2/src/oxutils/currency/models.py +109 -0
- oxutils-0.3.2/src/oxutils/currency/signals.py +25 -0
- {oxutils-0.1.22 → oxutils-0.3.2}/src/oxutils/enums/__init__.py +4 -0
- {oxutils-0.1.22 → oxutils-0.3.2}/src/oxutils/jwt/auth.py +28 -42
- {oxutils-0.1.22 → oxutils-0.3.2}/src/oxutils/jwt/middleware.py +72 -78
- oxutils-0.3.2/src/oxutils/models/__init__.py +31 -0
- {oxutils-0.1.22 → oxutils-0.3.2}/src/oxutils/models/base.py +31 -38
- oxutils-0.3.2/src/oxutils/models/change_tracker.py +64 -0
- {oxutils-0.1.22 → oxutils-0.3.2}/src/oxutils/models/invoice.py +168 -216
- {oxutils-0.1.22 → oxutils-0.3.2}/src/oxutils/oxiliere/authorization.py +4 -9
- oxutils-0.3.2/src/oxutils/oxiliere/enums.py +10 -0
- oxutils-0.3.2/src/oxutils/oxiliere/management/__init__.py +0 -0
- oxutils-0.3.2/src/oxutils/oxiliere/management/commands/__init__.py +0 -0
- {oxutils-0.1.22 → oxutils-0.3.2}/src/oxutils/oxiliere/management/commands/grant_tenant_owners.py +3 -2
- {oxutils-0.1.22 → oxutils-0.3.2}/src/oxutils/oxiliere/middleware.py +121 -56
- oxutils-0.3.2/src/oxutils/oxiliere/migrations/__init__.py +0 -0
- oxutils-0.3.2/src/oxutils/oxiliere/models.py +265 -0
- {oxutils-0.1.22 → oxutils-0.3.2}/src/oxutils/oxiliere/services.py +64 -25
- oxutils-0.3.2/src/oxutils/oxiliere/signals.py +106 -0
- oxutils-0.3.2/src/oxutils/pagination/__init__.py +0 -0
- oxutils-0.3.2/src/oxutils/permissions/__init__.py +0 -0
- oxutils-0.3.2/src/oxutils/permissions/constants.py +0 -0
- oxutils-0.3.2/src/oxutils/permissions/management/__init__.py +0 -0
- oxutils-0.3.2/src/oxutils/permissions/management/commands/__init__.py +0 -0
- oxutils-0.3.2/src/oxutils/permissions/migrations/__init__.py +0 -0
- {oxutils-0.1.22 → oxutils-0.3.2}/src/oxutils/permissions/models.py +21 -25
- {oxutils-0.1.22 → oxutils-0.3.2}/src/oxutils/permissions/utils.py +194 -227
- oxutils-0.3.2/src/oxutils/py.typed +0 -0
- oxutils-0.3.2/src/oxutils/s3.py +110 -0
- oxutils-0.3.2/src/oxutils/templates/account/email/base_message.txt +7 -0
- oxutils-0.3.2/src/oxutils/templates/email/password/reset_email_message.html +97 -0
- oxutils-0.3.2/src/oxutils/templates/email/password/reset_email_message.txt +14 -0
- oxutils-0.3.2/src/oxutils/templates/email/password/reset_email_subject.txt +2 -0
- oxutils-0.3.2/src/oxutils/templates/email/signup/email_message.html +88 -0
- oxutils-0.3.2/src/oxutils/templates/email/signup/email_subject.txt +2 -0
- oxutils-0.3.2/src/oxutils/templates/verified.html +81 -0
- oxutils-0.3.2/src/oxutils/users/__init__.py +0 -0
- oxutils-0.3.2/src/oxutils/users/admin.py +3 -0
- oxutils-0.3.2/src/oxutils/users/migrations/__init__.py +0 -0
- oxutils-0.1.22/src/oxutils/__init__.py +0 -23
- oxutils-0.1.22/src/oxutils/currency/models.py +0 -100
- oxutils-0.1.22/src/oxutils/models/__init__.py +0 -3
- oxutils-0.1.22/src/oxutils/oxiliere/enums.py +0 -10
- oxutils-0.1.22/src/oxutils/oxiliere/models.py +0 -192
- oxutils-0.1.22/src/oxutils/oxiliere/signals.py +0 -5
- {oxutils-0.1.22 → oxutils-0.3.2}/README.md +0 -0
- {oxutils-0.1.22 → oxutils-0.3.2}/src/oxutils/apps.py +0 -0
- {oxutils-0.1.22 → oxutils-0.3.2}/src/oxutils/audit/__init__.py +0 -0
- {oxutils-0.1.22 → oxutils-0.3.2}/src/oxutils/audit/apps.py +0 -0
- {oxutils-0.1.22 → oxutils-0.3.2}/src/oxutils/audit/export.py +0 -0
- {oxutils-0.1.22 → oxutils-0.3.2}/src/oxutils/audit/locale/fr/LC_MESSAGES/django.mo +0 -0
- {oxutils-0.1.22 → oxutils-0.3.2}/src/oxutils/audit/locale/fr/LC_MESSAGES/django.po +0 -0
- {oxutils-0.1.22 → oxutils-0.3.2}/src/oxutils/audit/masks.py +0 -0
- {oxutils-0.1.22 → oxutils-0.3.2}/src/oxutils/audit/migrations/0001_initial.py +0 -0
- {oxutils-0.1.22 → oxutils-0.3.2}/src/oxutils/audit/migrations/__init__.py +0 -0
- {oxutils-0.1.22 → oxutils-0.3.2}/src/oxutils/audit/models.py +0 -0
- {oxutils-0.1.22 → oxutils-0.3.2}/src/oxutils/audit/settings.py +0 -0
- {oxutils-0.1.22 → oxutils-0.3.2}/src/oxutils/audit/utils.py +0 -0
- {oxutils-0.1.22/src/oxutils/context → oxutils-0.3.2/src/oxutils/auth}/__init__.py +0 -0
- {oxutils-0.1.22/src/oxutils/oxiliere → oxutils-0.3.2/src/oxutils/auth}/admin.py +0 -0
- {oxutils-0.1.22/src/oxutils/currency → oxutils-0.3.2/src/oxutils/auth/emails}/__init__.py +0 -0
- {oxutils-0.1.22/src/oxutils/currency/management → oxutils-0.3.2/src/oxutils/auth/invitations}/__init__.py +0 -0
- {oxutils-0.1.22/src/oxutils/currency/management/commands → oxutils-0.3.2/src/oxutils/auth/mfa/base}/__init__.py +0 -0
- {oxutils-0.1.22/src/oxutils/currency/migrations → oxutils-0.3.2/src/oxutils/auth/mfa/recovery_codes}/__init__.py +0 -0
- /oxutils-0.1.22/src/oxutils/jwt/__init__.py → /oxutils-0.3.2/src/oxutils/auth/mfa/totp/__ini__.py +0 -0
- {oxutils-0.1.22/src/oxutils/mixins → oxutils-0.3.2/src/oxutils/auth/migrations}/__init__.py +0 -0
- {oxutils-0.1.22/src/oxutils/oxiliere → oxutils-0.3.2/src/oxutils/auth/password_reset}/__init__.py +0 -0
- {oxutils-0.1.22/src/oxutils/oxiliere/management → oxutils-0.3.2/src/oxutils/auth/registration}/__init__.py +0 -0
- {oxutils-0.1.22/src/oxutils/oxiliere/management/commands → oxutils-0.3.2/src/oxutils/auth/sessions}/__init__.py +0 -0
- {oxutils-0.1.22/src/oxutils/oxiliere/migrations → oxutils-0.3.2/src/oxutils/auth/templatetags}/__init__.py +0 -0
- {oxutils-0.1.22/src/oxutils/pagination → oxutils-0.3.2/src/oxutils/auth/tokens}/__init__.py +0 -0
- {oxutils-0.1.22 → oxutils-0.3.2}/src/oxutils/celery/__init__.py +0 -0
- {oxutils-0.1.22 → oxutils-0.3.2}/src/oxutils/celery/base.py +0 -0
- {oxutils-0.1.22 → oxutils-0.3.2}/src/oxutils/celery/settings.py +0 -0
- {oxutils-0.1.22 → oxutils-0.3.2}/src/oxutils/conf.py +0 -0
- {oxutils-0.1.22 → oxutils-0.3.2}/src/oxutils/constants.py +0 -0
- {oxutils-0.1.22/src/oxutils/permissions → oxutils-0.3.2/src/oxutils/context}/__init__.py +0 -0
- {oxutils-0.1.22 → oxutils-0.3.2}/src/oxutils/context/site_name_processor.py +0 -0
- {oxutils-0.1.22/src/oxutils/permissions/management → oxutils-0.3.2/src/oxutils/currency}/__init__.py +0 -0
- {oxutils-0.1.22 → oxutils-0.3.2}/src/oxutils/currency/admin.py +0 -0
- {oxutils-0.1.22 → oxutils-0.3.2}/src/oxutils/currency/apps.py +0 -0
- {oxutils-0.1.22 → oxutils-0.3.2}/src/oxutils/currency/caches.py +0 -0
- {oxutils-0.1.22 → oxutils-0.3.2}/src/oxutils/currency/controllers.py +0 -0
- {oxutils-0.1.22 → oxutils-0.3.2}/src/oxutils/currency/enums.py +0 -0
- {oxutils-0.1.22/src/oxutils/permissions/management/commands → oxutils-0.3.2/src/oxutils/currency/management}/__init__.py +0 -0
- {oxutils-0.1.22/src/oxutils/permissions/migrations → oxutils-0.3.2/src/oxutils/currency/management/commands}/__init__.py +0 -0
- {oxutils-0.1.22 → oxutils-0.3.2}/src/oxutils/currency/management/commands/sync_currency.py +0 -0
- {oxutils-0.1.22 → oxutils-0.3.2}/src/oxutils/currency/migrations/0001_initial.py +0 -0
- {oxutils-0.1.22/src/oxutils/users → oxutils-0.3.2/src/oxutils/currency/migrations}/__init__.py +0 -0
- {oxutils-0.1.22 → oxutils-0.3.2}/src/oxutils/currency/schemas.py +0 -0
- {oxutils-0.1.22 → oxutils-0.3.2}/src/oxutils/currency/tests.py +0 -0
- {oxutils-0.1.22 → oxutils-0.3.2}/src/oxutils/currency/utils.py +0 -0
- {oxutils-0.1.22 → oxutils-0.3.2}/src/oxutils/enums/audit.py +0 -0
- {oxutils-0.1.22 → oxutils-0.3.2}/src/oxutils/enums/invoices.py +0 -0
- {oxutils-0.1.22 → oxutils-0.3.2}/src/oxutils/exceptions.py +0 -0
- {oxutils-0.1.22 → oxutils-0.3.2}/src/oxutils/functions.py +0 -0
- {oxutils-0.1.22/src/oxutils/users/migrations → oxutils-0.3.2/src/oxutils/jwt}/__init__.py +0 -0
- {oxutils-0.1.22 → oxutils-0.3.2}/src/oxutils/jwt/models.py +0 -0
- {oxutils-0.1.22 → oxutils-0.3.2}/src/oxutils/jwt/tokens.py +0 -0
- {oxutils-0.1.22 → oxutils-0.3.2}/src/oxutils/jwt/utils.py +0 -0
- {oxutils-0.1.22 → oxutils-0.3.2}/src/oxutils/logger/__init__.py +0 -0
- {oxutils-0.1.22 → oxutils-0.3.2}/src/oxutils/logger/receivers.py +0 -0
- {oxutils-0.1.22 → oxutils-0.3.2}/src/oxutils/logger/settings.py +0 -0
- /oxutils-0.1.22/src/oxutils/permissions/constants.py → /oxutils-0.3.2/src/oxutils/mixins/__init__.py +0 -0
- {oxutils-0.1.22 → oxutils-0.3.2}/src/oxutils/mixins/base.py +0 -0
- {oxutils-0.1.22 → oxutils-0.3.2}/src/oxutils/mixins/schemas.py +0 -0
- {oxutils-0.1.22 → oxutils-0.3.2}/src/oxutils/mixins/services.py +0 -0
- {oxutils-0.1.22 → oxutils-0.3.2}/src/oxutils/models/billing.py +0 -0
- {oxutils-0.1.22 → oxutils-0.3.2}/src/oxutils/models/fields.py +0 -0
- /oxutils-0.1.22/src/oxutils/py.typed → /oxutils-0.3.2/src/oxutils/oxiliere/__init__.py +0 -0
- {oxutils-0.1.22/src/oxutils/permissions → oxutils-0.3.2/src/oxutils/oxiliere}/admin.py +0 -0
- {oxutils-0.1.22 → oxutils-0.3.2}/src/oxutils/oxiliere/apps.py +0 -0
- {oxutils-0.1.22 → oxutils-0.3.2}/src/oxutils/oxiliere/cacheops.py +0 -0
- {oxutils-0.1.22 → oxutils-0.3.2}/src/oxutils/oxiliere/caches.py +0 -0
- {oxutils-0.1.22 → oxutils-0.3.2}/src/oxutils/oxiliere/checks.py +0 -0
- {oxutils-0.1.22 → oxutils-0.3.2}/src/oxutils/oxiliere/constants.py +0 -0
- {oxutils-0.1.22 → oxutils-0.3.2}/src/oxutils/oxiliere/context.py +0 -0
- {oxutils-0.1.22 → oxutils-0.3.2}/src/oxutils/oxiliere/controllers.py +0 -0
- {oxutils-0.1.22 → oxutils-0.3.2}/src/oxutils/oxiliere/exceptions.py +0 -0
- {oxutils-0.1.22 → oxutils-0.3.2}/src/oxutils/oxiliere/locale/fr/LC_MESSAGES/django.mo +0 -0
- {oxutils-0.1.22 → oxutils-0.3.2}/src/oxutils/oxiliere/locale/fr/LC_MESSAGES/django.po +0 -0
- {oxutils-0.1.22 → oxutils-0.3.2}/src/oxutils/oxiliere/management/commands/init_oxiliere_system.py +0 -0
- {oxutils-0.1.22 → oxutils-0.3.2}/src/oxutils/oxiliere/permissions.py +0 -0
- {oxutils-0.1.22 → oxutils-0.3.2}/src/oxutils/oxiliere/schemas.py +0 -0
- {oxutils-0.1.22 → oxutils-0.3.2}/src/oxutils/oxiliere/settings.py +0 -0
- {oxutils-0.1.22 → oxutils-0.3.2}/src/oxutils/oxiliere/tests.py +0 -0
- {oxutils-0.1.22 → oxutils-0.3.2}/src/oxutils/oxiliere/utils.py +0 -0
- {oxutils-0.1.22 → oxutils-0.3.2}/src/oxutils/pagination/cursor.py +0 -0
- {oxutils-0.1.22 → oxutils-0.3.2}/src/oxutils/pdf/__init__.py +0 -0
- {oxutils-0.1.22 → oxutils-0.3.2}/src/oxutils/pdf/printer.py +0 -0
- {oxutils-0.1.22 → oxutils-0.3.2}/src/oxutils/pdf/utils.py +0 -0
- {oxutils-0.1.22 → oxutils-0.3.2}/src/oxutils/pdf/views.py +0 -0
- {oxutils-0.1.22 → oxutils-0.3.2}/src/oxutils/permissions/actions.py +0 -0
- {oxutils-0.1.22/src/oxutils/users → oxutils-0.3.2/src/oxutils/permissions}/admin.py +0 -0
- {oxutils-0.1.22 → oxutils-0.3.2}/src/oxutils/permissions/apps.py +0 -0
- {oxutils-0.1.22 → oxutils-0.3.2}/src/oxutils/permissions/caches.py +0 -0
- {oxutils-0.1.22 → oxutils-0.3.2}/src/oxutils/permissions/checks.py +0 -0
- {oxutils-0.1.22 → oxutils-0.3.2}/src/oxutils/permissions/controllers.py +0 -0
- {oxutils-0.1.22 → oxutils-0.3.2}/src/oxutils/permissions/exceptions.py +0 -0
- {oxutils-0.1.22 → oxutils-0.3.2}/src/oxutils/permissions/locale/fr/LC_MESSAGES/django.mo +0 -0
- {oxutils-0.1.22 → oxutils-0.3.2}/src/oxutils/permissions/locale/fr/LC_MESSAGES/django.po +0 -0
- {oxutils-0.1.22 → oxutils-0.3.2}/src/oxutils/permissions/management/commands/load_permission_preset.py +0 -0
- {oxutils-0.1.22 → oxutils-0.3.2}/src/oxutils/permissions/migrations/0001_initial.py +0 -0
- {oxutils-0.1.22 → oxutils-0.3.2}/src/oxutils/permissions/migrations/0002_alter_grant_role.py +0 -0
- {oxutils-0.1.22 → oxutils-0.3.2}/src/oxutils/permissions/migrations/0003_alter_grant_options_alter_group_options_and_more.py +0 -0
- {oxutils-0.1.22 → oxutils-0.3.2}/src/oxutils/permissions/migrations/0004_group_app_group_permissions_app_9a9924_idx.py +0 -0
- {oxutils-0.1.22 → oxutils-0.3.2}/src/oxutils/permissions/migrations/0005_role_app.py +0 -0
- {oxutils-0.1.22 → oxutils-0.3.2}/src/oxutils/permissions/migrations/0006_alter_rolegrant_options_and_more.py +0 -0
- {oxutils-0.1.22 → oxutils-0.3.2}/src/oxutils/permissions/migrations/0007_grant_locked_alter_grant_role_and_more.py +0 -0
- {oxutils-0.1.22 → oxutils-0.3.2}/src/oxutils/permissions/migrations/0008_remove_grant_unique_user_scope_role_and_more.py +0 -0
- {oxutils-0.1.22 → oxutils-0.3.2}/src/oxutils/permissions/perms.py +0 -0
- {oxutils-0.1.22 → oxutils-0.3.2}/src/oxutils/permissions/queryset.py +0 -0
- {oxutils-0.1.22 → oxutils-0.3.2}/src/oxutils/permissions/schemas.py +0 -0
- {oxutils-0.1.22 → oxutils-0.3.2}/src/oxutils/permissions/services.py +0 -0
- {oxutils-0.1.22 → oxutils-0.3.2}/src/oxutils/permissions/tests.py +0 -0
- {oxutils-0.1.22 → oxutils-0.3.2}/src/oxutils/settings.py +0 -0
- {oxutils-0.1.22 → oxutils-0.3.2}/src/oxutils/types.py +0 -0
- {oxutils-0.1.22 → oxutils-0.3.2}/src/oxutils/users/apps.py +0 -0
- {oxutils-0.1.22 → oxutils-0.3.2}/src/oxutils/users/locale/fr/LC_MESSAGES/django.mo +0 -0
- {oxutils-0.1.22 → oxutils-0.3.2}/src/oxutils/users/locale/fr/LC_MESSAGES/django.po +0 -0
- {oxutils-0.1.22 → oxutils-0.3.2}/src/oxutils/users/migrations/0001_initial.py +0 -0
- {oxutils-0.1.22 → oxutils-0.3.2}/src/oxutils/users/migrations/0002_alter_user_first_name_alter_user_last_name.py +0 -0
- {oxutils-0.1.22 → oxutils-0.3.2}/src/oxutils/users/migrations/0003_user_photo.py +0 -0
- {oxutils-0.1.22 → oxutils-0.3.2}/src/oxutils/users/migrations/0004_alter_user_options.py +0 -0
- {oxutils-0.1.22 → oxutils-0.3.2}/src/oxutils/users/models.py +0 -0
- {oxutils-0.1.22 → oxutils-0.3.2}/src/oxutils/users/tests.py +0 -0
- {oxutils-0.1.22 → oxutils-0.3.2}/src/oxutils/users/utils.py +0 -0
- {oxutils-0.1.22 → oxutils-0.3.2}/src/oxutils/utils.py +0 -0
|
@@ -1,16 +1,16 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: oxutils
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.3.2
|
|
4
4
|
Summary: Production-ready utilities for Django applications in the Oxiliere ecosystem
|
|
5
5
|
Keywords: django,utilities,jwt,audit,logging,celery,structlog
|
|
6
6
|
Author: Edimedia Mutoke
|
|
7
7
|
Author-email: Edimedia Mutoke <eddycondor07@gmail.com>
|
|
8
|
-
License-Expression:
|
|
8
|
+
License-Expression: LGPL-3.0-only
|
|
9
9
|
Classifier: Development Status :: 4 - Beta
|
|
10
10
|
Classifier: Framework :: Django
|
|
11
11
|
Classifier: Framework :: Django :: 5.0
|
|
12
12
|
Classifier: Intended Audience :: Developers
|
|
13
|
-
Classifier: License :: OSI Approved ::
|
|
13
|
+
Classifier: License :: OSI Approved :: GNU Lesser General Public License v3 (LGPLv3)
|
|
14
14
|
Classifier: Operating System :: OS Independent
|
|
15
15
|
Classifier: Programming Language :: Python :: 3
|
|
16
16
|
Classifier: Programming Language :: Python :: 3.12
|
|
@@ -20,14 +20,17 @@ Requires-Dist: bcc-rates==1.1.1
|
|
|
20
20
|
Requires-Dist: boto3>=1.41.5
|
|
21
21
|
Requires-Dist: celery>=5.5.3
|
|
22
22
|
Requires-Dist: cryptography>=46.0.3
|
|
23
|
+
Requires-Dist: django-allauth[idp-oidc,mfa]>=65.18.0
|
|
23
24
|
Requires-Dist: django-auditlog>=3.3.0
|
|
24
25
|
Requires-Dist: django-celery-results>=2.6.0
|
|
25
26
|
Requires-Dist: django-extensions>=4.1
|
|
26
27
|
Requires-Dist: django-ninja>=1.5.0
|
|
27
28
|
Requires-Dist: django-ninja-extra>=0.30.6
|
|
28
29
|
Requires-Dist: django-structlog[celery]>=10.0.0
|
|
30
|
+
Requires-Dist: django-user-agents>=0.4.0
|
|
29
31
|
Requires-Dist: jwcrypto>=1.5.6
|
|
30
32
|
Requires-Dist: pydantic-settings>=2.12.0
|
|
33
|
+
Requires-Dist: pydantic[email]>=2.12.5
|
|
31
34
|
Requires-Dist: pyjwt>=2.10.1
|
|
32
35
|
Requires-Dist: requests>=2.32.5
|
|
33
36
|
Requires-Dist: bcc-rates>=1.1.0 ; extra == 'all'
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "oxutils"
|
|
3
|
-
version = "0.
|
|
3
|
+
version = "0.3.2"
|
|
4
4
|
description = "Production-ready utilities for Django applications in the Oxiliere ecosystem"
|
|
5
5
|
readme = "README.md"
|
|
6
|
-
license = "
|
|
6
|
+
license = "LGPL-3.0-only"
|
|
7
7
|
authors = [
|
|
8
8
|
{ name = "Edimedia Mutoke", email = "eddycondor07@gmail.com" }
|
|
9
9
|
]
|
|
@@ -14,7 +14,7 @@ classifiers = [
|
|
|
14
14
|
"Framework :: Django",
|
|
15
15
|
"Framework :: Django :: 5.0",
|
|
16
16
|
"Intended Audience :: Developers",
|
|
17
|
-
"License :: OSI Approved ::
|
|
17
|
+
"License :: OSI Approved :: GNU Lesser General Public License v3 (LGPLv3)",
|
|
18
18
|
"Operating System :: OS Independent",
|
|
19
19
|
"Programming Language :: Python :: 3",
|
|
20
20
|
"Programming Language :: Python :: 3.12",
|
|
@@ -26,14 +26,17 @@ dependencies = [
|
|
|
26
26
|
"boto3>=1.41.5",
|
|
27
27
|
"celery>=5.5.3",
|
|
28
28
|
"cryptography>=46.0.3",
|
|
29
|
+
"django-allauth[idp-oidc,mfa]>=65.18.0",
|
|
29
30
|
"django-auditlog>=3.3.0",
|
|
30
31
|
"django-celery-results>=2.6.0",
|
|
31
32
|
"django-extensions>=4.1",
|
|
32
33
|
"django-ninja>=1.5.0",
|
|
33
34
|
"django-ninja-extra>=0.30.6",
|
|
34
35
|
"django-structlog[celery]>=10.0.0",
|
|
36
|
+
"django-user-agents>=0.4.0",
|
|
35
37
|
"jwcrypto>=1.5.6",
|
|
36
38
|
"pydantic-settings>=2.12.0",
|
|
39
|
+
"pydantic[email]>=2.12.5",
|
|
37
40
|
"pyjwt>=2.10.1",
|
|
38
41
|
"requests>=2.32.5",
|
|
39
42
|
]
|
|
@@ -128,5 +131,12 @@ pythonpath = [".", "src"]
|
|
|
128
131
|
python_files = ["test_*.py"]
|
|
129
132
|
python_classes = ["Test*"]
|
|
130
133
|
python_functions = ["test_*"]
|
|
131
|
-
addopts = "-v --tb=short --ds=tests.settings"
|
|
134
|
+
addopts = "-v --tb=short --ds=tests.settings --strict-markers"
|
|
132
135
|
testpaths = ["tests"]
|
|
136
|
+
filterwarnings = [
|
|
137
|
+
"ignore::DeprecationWarning:django.*",
|
|
138
|
+
"ignore::DeprecationWarning:pydantic.*",
|
|
139
|
+
"ignore::PendingDeprecationWarning:",
|
|
140
|
+
"ignore::pydantic.PydanticDeprecatedSince20:ninja.*",
|
|
141
|
+
"ignore::pydantic.PydanticDeprecatedSince20:ninja_extra.*",
|
|
142
|
+
]
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
"""OxUtils - Production-ready utilities for Django applications.
|
|
2
|
+
|
|
3
|
+
This package provides:
|
|
4
|
+
- JWT authentication with JWKS support
|
|
5
|
+
- Multi-tenant architecture (django-tenants integration, middleware, signals)
|
|
6
|
+
- Auth system: invitations, MFA, password reset, registration, sessions
|
|
7
|
+
- Structured logging with correlation IDs
|
|
8
|
+
- Audit system with S3 export
|
|
9
|
+
- Celery integration
|
|
10
|
+
- Django model mixins (ChangeTracker, CookieToken, SafeDelete)
|
|
11
|
+
- Permission management & authorization
|
|
12
|
+
- PDF generation
|
|
13
|
+
- Pagination utilities
|
|
14
|
+
- Currency utilities
|
|
15
|
+
- Custom exceptions, enums, and type definitions
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
__version__ = "0.3.1"
|
|
19
|
+
|
|
20
|
+
from oxutils.conf import AUDIT_MIDDLEWARE, UTILS_APPS
|
|
21
|
+
from oxutils.settings import oxi_settings
|
|
22
|
+
|
|
23
|
+
__all__ = [
|
|
24
|
+
"oxi_settings",
|
|
25
|
+
"UTILS_APPS",
|
|
26
|
+
"AUDIT_MIDDLEWARE",
|
|
27
|
+
"__version__",
|
|
28
|
+
]
|
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
from allauth.account import app_settings as allauth_app_settings
|
|
2
|
+
from allauth.account.adapter import DefaultAccountAdapter
|
|
3
|
+
from allauth.account.models import EmailAddress
|
|
4
|
+
from allauth.core import context as allauth_ctx
|
|
5
|
+
from django.contrib.sites.shortcuts import get_current_site
|
|
6
|
+
from django.core.mail import EmailMessage, EmailMultiAlternatives
|
|
7
|
+
from django.template import TemplateDoesNotExist
|
|
8
|
+
from django.template.loader import render_to_string
|
|
9
|
+
from django.utils.translation import gettext_lazy as _
|
|
10
|
+
|
|
11
|
+
from oxutils.auth.utils import format_confirm_email_url, get_template_path
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class JWTAllAuthAdapter(DefaultAccountAdapter):
|
|
15
|
+
"""
|
|
16
|
+
Custom account adapter extending allauth's DefaultAccountAdapter with JWT-specific email handling.
|
|
17
|
+
|
|
18
|
+
Provides enhanced email confirmation functionality with template path customization
|
|
19
|
+
and JWT-related email content handling.
|
|
20
|
+
|
|
21
|
+
Key Features:
|
|
22
|
+
|
|
23
|
+
- Email normalization (trimming and lowercasing)
|
|
24
|
+
- Customizable template paths for verification emails
|
|
25
|
+
- Dual template support (HTML/text) with fallback handling
|
|
26
|
+
- Integration with JWT verification workflows
|
|
27
|
+
"""
|
|
28
|
+
|
|
29
|
+
def clean_email(self, email):
|
|
30
|
+
"""
|
|
31
|
+
Normalize email addresses by trimming whitespace and converting to lowercase.
|
|
32
|
+
|
|
33
|
+
Args:
|
|
34
|
+
email (str): Raw email input
|
|
35
|
+
|
|
36
|
+
Returns:
|
|
37
|
+
str: Normalized email address
|
|
38
|
+
"""
|
|
39
|
+
email = super().clean_email(email)
|
|
40
|
+
email = email.strip().lower()
|
|
41
|
+
|
|
42
|
+
if allauth_app_settings.UNIQUE_EMAIL:
|
|
43
|
+
if EmailAddress.objects.filter(email=email, verified=True).exists():
|
|
44
|
+
raise ValueError(_("A user is already registered with this e-mail address."))
|
|
45
|
+
# delete previous non-verified registration attempts
|
|
46
|
+
EmailAddress.objects.filter(email=email, verified=False).delete()
|
|
47
|
+
|
|
48
|
+
return email
|
|
49
|
+
|
|
50
|
+
def populate_username(self, request, user):
|
|
51
|
+
"""
|
|
52
|
+
Override username population since our User model doesn't use usernames.
|
|
53
|
+
|
|
54
|
+
Our User model has username = None and uses email as USERNAME_FIELD,
|
|
55
|
+
so we skip the username generation process entirely.
|
|
56
|
+
|
|
57
|
+
Args:
|
|
58
|
+
request (HttpRequest): Current request object
|
|
59
|
+
user (User): User instance to populate
|
|
60
|
+
"""
|
|
61
|
+
# Do nothing - our User model doesn't use usernames
|
|
62
|
+
pass
|
|
63
|
+
|
|
64
|
+
def get_email_confirmation_url(self, request, emailconfirmation):
|
|
65
|
+
return format_confirm_email_url(emailconfirmation.key)
|
|
66
|
+
|
|
67
|
+
def send_confirmation_mail(self, request, emailconfirmation, signup):
|
|
68
|
+
"""
|
|
69
|
+
Generate and send email confirmation message with context customization.
|
|
70
|
+
|
|
71
|
+
Context Includes:
|
|
72
|
+
|
|
73
|
+
- User object
|
|
74
|
+
- Verification code or URL (based on EMAIL_VERIFICATION_BY_CODE_ENABLED)
|
|
75
|
+
- Site-specific information
|
|
76
|
+
|
|
77
|
+
Args:
|
|
78
|
+
request (HttpRequest): Current request object
|
|
79
|
+
emailconfirmation (EmailConfirmation): Email confirmation instance
|
|
80
|
+
signup (bool): Flag indicating if this is a signup confirmation
|
|
81
|
+
|
|
82
|
+
Returns:
|
|
83
|
+
str: Confirmation key used in the email
|
|
84
|
+
"""
|
|
85
|
+
ctx = {
|
|
86
|
+
"user": emailconfirmation.email_address.user,
|
|
87
|
+
}
|
|
88
|
+
if allauth_app_settings.EMAIL_VERIFICATION_BY_CODE_ENABLED:
|
|
89
|
+
ctx.update({"code": emailconfirmation.key})
|
|
90
|
+
else:
|
|
91
|
+
ctx.update(
|
|
92
|
+
{
|
|
93
|
+
"key": emailconfirmation.key,
|
|
94
|
+
"activate_url": self.get_email_confirmation_url(request, emailconfirmation),
|
|
95
|
+
}
|
|
96
|
+
)
|
|
97
|
+
if signup:
|
|
98
|
+
email_template = "email/signup/email_signup"
|
|
99
|
+
template_path = get_template_path(
|
|
100
|
+
"EMAIL_VERIFICATION", "email/signup/email_message.html"
|
|
101
|
+
)
|
|
102
|
+
subject_path = get_template_path(
|
|
103
|
+
"EMAIL_VERIFICATION_SUBJECT", "email/signup/email_subject.txt"
|
|
104
|
+
)
|
|
105
|
+
else:
|
|
106
|
+
email_template = "account/email/email_confirmation"
|
|
107
|
+
template_path = None
|
|
108
|
+
subject_path = None
|
|
109
|
+
self.send_mail(
|
|
110
|
+
email_template,
|
|
111
|
+
emailconfirmation.email_address.email,
|
|
112
|
+
ctx,
|
|
113
|
+
subject_path=subject_path,
|
|
114
|
+
template_path=template_path,
|
|
115
|
+
)
|
|
116
|
+
return ctx["key"]
|
|
117
|
+
|
|
118
|
+
def send_mail(self, template_prefix, email, context, subject_path=None, template_path=None):
|
|
119
|
+
"""
|
|
120
|
+
Construct and send email using template configuration.
|
|
121
|
+
|
|
122
|
+
Enhances Context With:
|
|
123
|
+
|
|
124
|
+
- Current site information
|
|
125
|
+
- Recipient email address
|
|
126
|
+
|
|
127
|
+
Args:
|
|
128
|
+
template_prefix (str): Base path for template lookup
|
|
129
|
+
email (str|list): Recipient email address(es)
|
|
130
|
+
context (dict): Template context variables
|
|
131
|
+
subject_path (str, optional): Custom path for subject template
|
|
132
|
+
template_path (str, optional): Custom path for body template
|
|
133
|
+
"""
|
|
134
|
+
ctx = {
|
|
135
|
+
"email": email,
|
|
136
|
+
"current_site": get_current_site(allauth_ctx.request),
|
|
137
|
+
}
|
|
138
|
+
ctx.update(context)
|
|
139
|
+
msg = self.render_mail(
|
|
140
|
+
template_prefix, email, ctx, subject_path=subject_path, template_path=template_path
|
|
141
|
+
)
|
|
142
|
+
msg.send()
|
|
143
|
+
|
|
144
|
+
def render_mail(
|
|
145
|
+
self, template_prefix, email, context, headers=None, subject_path=None, template_path=None
|
|
146
|
+
):
|
|
147
|
+
"""
|
|
148
|
+
Render email message with support for multiple template formats and custom paths.
|
|
149
|
+
|
|
150
|
+
Behavior:
|
|
151
|
+
|
|
152
|
+
- Generates multipart emails when both HTML and text templates exist
|
|
153
|
+
- Uses custom template paths when provided
|
|
154
|
+
- Automatically formats email subject
|
|
155
|
+
- Supports HTML email content as primary when specified
|
|
156
|
+
|
|
157
|
+
Args:
|
|
158
|
+
template_prefix (str): Base template path prefix
|
|
159
|
+
email (str|list): Recipient email address(es)
|
|
160
|
+
context (dict): Template context variables
|
|
161
|
+
headers (dict, optional): Custom email headers
|
|
162
|
+
subject_path (str, optional): Override path for subject template
|
|
163
|
+
template_path (str, optional): Override path for body template
|
|
164
|
+
|
|
165
|
+
Returns:
|
|
166
|
+
EmailMessage: Configured email message object
|
|
167
|
+
|
|
168
|
+
Raises:
|
|
169
|
+
TemplateDoesNotExist: If no valid template can be found
|
|
170
|
+
"""
|
|
171
|
+
to = [email] if isinstance(email, str) else email
|
|
172
|
+
if subject_path is None:
|
|
173
|
+
subject_path = "{0}_subject.txt".format(template_prefix)
|
|
174
|
+
subject = render_to_string(subject_path, context)
|
|
175
|
+
# remove superfluous line breaks
|
|
176
|
+
subject = " ".join(subject.splitlines()).strip()
|
|
177
|
+
subject = self.format_email_subject(subject)
|
|
178
|
+
|
|
179
|
+
from_email = self.get_from_email()
|
|
180
|
+
|
|
181
|
+
if template_path is None:
|
|
182
|
+
bodies = {}
|
|
183
|
+
html_ext = allauth_app_settings.TEMPLATE_EXTENSION
|
|
184
|
+
for ext in [html_ext, "txt"]:
|
|
185
|
+
try:
|
|
186
|
+
template_name = "{0}_message.{1}".format(template_prefix, ext)
|
|
187
|
+
bodies[ext] = render_to_string(
|
|
188
|
+
template_name,
|
|
189
|
+
context,
|
|
190
|
+
allauth_ctx.request,
|
|
191
|
+
).strip()
|
|
192
|
+
except TemplateDoesNotExist:
|
|
193
|
+
if ext == "txt" and not bodies:
|
|
194
|
+
# We need at least one body
|
|
195
|
+
raise
|
|
196
|
+
else:
|
|
197
|
+
html_ext = "html"
|
|
198
|
+
bodies = {
|
|
199
|
+
html_ext: render_to_string(
|
|
200
|
+
template_path,
|
|
201
|
+
context,
|
|
202
|
+
allauth_ctx.request,
|
|
203
|
+
).strip()
|
|
204
|
+
}
|
|
205
|
+
if "txt" in bodies:
|
|
206
|
+
msg = EmailMultiAlternatives(subject, bodies["txt"], from_email, to, headers=headers)
|
|
207
|
+
if html_ext in bodies:
|
|
208
|
+
msg.attach_alternative(bodies[html_ext], "text/html")
|
|
209
|
+
else:
|
|
210
|
+
msg = EmailMessage(subject, bodies[html_ext], from_email, to, headers=headers)
|
|
211
|
+
msg.content_subtype = "html" # Main content is now text/html
|
|
212
|
+
return msg
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
from django.conf import settings
|
|
2
|
+
|
|
3
|
+
from oxutils.auth.password_reset.schemas import PasswordResetSchema as DefaultPasswordResetSchema
|
|
4
|
+
from oxutils.auth.registration.schemas import RegisterSchema as DefaultRegisterSchema
|
|
5
|
+
from oxutils.auth.schemas import (
|
|
6
|
+
LoginSchema as DefaultLoginSchema,
|
|
7
|
+
)
|
|
8
|
+
from oxutils.auth.schemas import (
|
|
9
|
+
PasswordChangeSchema as DefaultPasswordChangeSchema,
|
|
10
|
+
)
|
|
11
|
+
from oxutils.auth.utils import import_callable
|
|
12
|
+
|
|
13
|
+
schemas = getattr(settings, "JWT_ALLAUTH_SCHEMAS", {})
|
|
14
|
+
|
|
15
|
+
LoginSerializer = import_callable(schemas.get("LOGIN_SCHEMA", DefaultLoginSchema))
|
|
16
|
+
|
|
17
|
+
PasswordResetSerializer = import_callable(
|
|
18
|
+
schemas.get("PASSWORD_RESET_SCHEMA", DefaultPasswordResetSchema)
|
|
19
|
+
)
|
|
20
|
+
|
|
21
|
+
PasswordChangeSerializer = import_callable(
|
|
22
|
+
schemas.get("PASSWORD_CHANGE_SCHEMA", DefaultPasswordChangeSchema)
|
|
23
|
+
)
|
|
24
|
+
|
|
25
|
+
RegisterSerializer = import_callable(schemas.get("REGISTER_SCHEMA", DefaultRegisterSchema))
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
PASS_RESET = 'PASS_RESET'
|
|
2
|
+
PASS_RESET_ACCESS = 'PASS_RESET_ACCESS'
|
|
3
|
+
TEMPLATE_PATHS = 'JWT_ALLAUTH_TEMPLATES'
|
|
4
|
+
|
|
5
|
+
EMAIL_VERIFIED_REDIRECT = 'EMAIL_VERIFIED_REDIRECT'
|
|
6
|
+
PASSWORD_RESET_REDIRECT = 'PASSWORD_RESET_REDIRECT'
|
|
7
|
+
|
|
8
|
+
PASS_RESET_COOKIE = 'password_reset_access_token'
|
|
9
|
+
|
|
10
|
+
FOR_USER = 'for_user'
|
|
11
|
+
ONE_TIME_PERMISSION = 'one_time_permission'
|
|
12
|
+
|
|
13
|
+
REFRESH_TOKEN_COOKIE = 'refresh_token'
|
|
14
|
+
ACCESS_TOKEN_COOKIE = 'access_token'
|
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
from typing import Optional
|
|
2
|
+
|
|
3
|
+
from django.http import HttpRequest
|
|
4
|
+
from django.utils.translation import gettext_lazy as _
|
|
5
|
+
from ninja_extra import ControllerBase, api_controller, http_post
|
|
6
|
+
from ninja_extra.permissions import IsAuthenticated
|
|
7
|
+
from ninja_extra.throttling import AnonRateThrottle, UserRateThrottle
|
|
8
|
+
from ninja_jwt.exceptions import InvalidToken, TokenError
|
|
9
|
+
from ninja_jwt.schema_control import SchemaControl
|
|
10
|
+
from ninja_jwt.settings import api_settings
|
|
11
|
+
|
|
12
|
+
from oxutils.auth.invitations.controllers import InvitationController
|
|
13
|
+
from oxutils.auth.mixins import CookieTokenMixin
|
|
14
|
+
from oxutils.auth.signals import user_logged_in, user_logged_out
|
|
15
|
+
from oxutils.auth.password_reset.controllers import (
|
|
16
|
+
PasswordResetConfirmController,
|
|
17
|
+
PasswordResetController,
|
|
18
|
+
ResetNewPasswordController,
|
|
19
|
+
)
|
|
20
|
+
from oxutils.auth.registration.controllers import (
|
|
21
|
+
EmailVerificationController,
|
|
22
|
+
RegisterController,
|
|
23
|
+
)
|
|
24
|
+
from oxutils.auth.schemas import (
|
|
25
|
+
LoginSchema,
|
|
26
|
+
PasswordChangeResponseSchema,
|
|
27
|
+
PasswordChangeSchema,
|
|
28
|
+
ReauthenticatePasswordSchema,
|
|
29
|
+
RemoveRefreshTokenSchema,
|
|
30
|
+
SendVerificationEmailSchema,
|
|
31
|
+
TokenObtainMFARequiredSchema,
|
|
32
|
+
TokenObtainPairOutputSchema,
|
|
33
|
+
TokenRefreshInputSchema,
|
|
34
|
+
TokenRefreshOutputSchema,
|
|
35
|
+
TokenRefreshSchema,
|
|
36
|
+
)
|
|
37
|
+
from oxutils.auth.utils import (
|
|
38
|
+
get_refresh_token,
|
|
39
|
+
get_user_agent,
|
|
40
|
+
load_user,
|
|
41
|
+
sensitive_post_parameters_m,
|
|
42
|
+
user_agent_dict,
|
|
43
|
+
)
|
|
44
|
+
from oxutils.exceptions import ExceptionCode
|
|
45
|
+
from oxutils.mixins.schemas import ResponseSchema
|
|
46
|
+
|
|
47
|
+
schema = SchemaControl(api_settings)
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
class LoginController(ControllerBase, CookieTokenMixin):
|
|
51
|
+
auto_import = False
|
|
52
|
+
|
|
53
|
+
@http_post(
|
|
54
|
+
"/login",
|
|
55
|
+
response={200: TokenObtainPairOutputSchema, 202: TokenObtainMFARequiredSchema},
|
|
56
|
+
url_name="token_obtain_pair",
|
|
57
|
+
operation_id="token_obtain_pair",
|
|
58
|
+
throttle=[AnonRateThrottle()],
|
|
59
|
+
auth=None,
|
|
60
|
+
)
|
|
61
|
+
def obtain_token(self, request: HttpRequest, user_token: LoginSchema):
|
|
62
|
+
user_token.check_user_authentication_rule()
|
|
63
|
+
schema = user_token.to_response_schema()
|
|
64
|
+
|
|
65
|
+
if schema.is_mfa_required():
|
|
66
|
+
return 202, schema
|
|
67
|
+
|
|
68
|
+
self.set_token_cookie(access_token=schema.access, refresh_token=schema.refresh)
|
|
69
|
+
|
|
70
|
+
user_logged_in.send_robust(sender=self.__class__, request=request, user=user_token._user)
|
|
71
|
+
|
|
72
|
+
return schema
|
|
73
|
+
|
|
74
|
+
@http_post(
|
|
75
|
+
"/verify-email",
|
|
76
|
+
response={
|
|
77
|
+
200: ResponseSchema,
|
|
78
|
+
},
|
|
79
|
+
throttle=[AnonRateThrottle()],
|
|
80
|
+
auth=None,
|
|
81
|
+
)
|
|
82
|
+
def send_verification_email(self, request: HttpRequest, payload: SendVerificationEmailSchema):
|
|
83
|
+
payload.send_verification_email(request)
|
|
84
|
+
|
|
85
|
+
return ResponseSchema(
|
|
86
|
+
code=ExceptionCode.SUCCESS, detail=str(_("Verification email sent successfully."))
|
|
87
|
+
)
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
class ReauthenticateController(ControllerBase, CookieTokenMixin):
|
|
91
|
+
auto_import = False
|
|
92
|
+
|
|
93
|
+
@http_post(
|
|
94
|
+
"/reauthenticate",
|
|
95
|
+
response={
|
|
96
|
+
200: TokenObtainPairOutputSchema,
|
|
97
|
+
},
|
|
98
|
+
url_name="reauthenticate_password",
|
|
99
|
+
operation_id="reauthenticate_password",
|
|
100
|
+
throttle=[UserRateThrottle()],
|
|
101
|
+
permissions=[IsAuthenticated],
|
|
102
|
+
)
|
|
103
|
+
@load_user
|
|
104
|
+
def reauthenticate(self, request: HttpRequest, payload: ReauthenticatePasswordSchema):
|
|
105
|
+
schema = payload.authenticate(request)
|
|
106
|
+
|
|
107
|
+
self.set_token_cookie(access_token=schema.access, refresh_token=schema.refresh)
|
|
108
|
+
|
|
109
|
+
user_logged_in.send_robust(sender=self.__class__, request=request, user=request.user)
|
|
110
|
+
|
|
111
|
+
return schema
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
class LogoutController(ControllerBase, CookieTokenMixin):
|
|
115
|
+
"""
|
|
116
|
+
Calls Django logout method and delete the Token object
|
|
117
|
+
assigned to the current User object.
|
|
118
|
+
|
|
119
|
+
Accepts/Returns nothing.
|
|
120
|
+
"""
|
|
121
|
+
|
|
122
|
+
@http_post("/logout", permissions=[IsAuthenticated], response={200: dict, 401: dict})
|
|
123
|
+
def logout(self, request: HttpRequest):
|
|
124
|
+
input_data = {"user": request.user.id}
|
|
125
|
+
|
|
126
|
+
refresh_token = get_refresh_token(request)
|
|
127
|
+
|
|
128
|
+
if not refresh_token:
|
|
129
|
+
raise InvalidToken
|
|
130
|
+
|
|
131
|
+
input_data["refresh"] = refresh_token
|
|
132
|
+
|
|
133
|
+
try:
|
|
134
|
+
RemoveRefreshTokenSchema.model_validate(input_data, context={"user": request.user.id})
|
|
135
|
+
self.remove_token_cookie()
|
|
136
|
+
user_logged_out.send_robust(sender=self.__class__, request=request, user=request.user)
|
|
137
|
+
return 200, {"detail": _("Successfully logged out.")}
|
|
138
|
+
except (TokenError, InvalidToken) as exc:
|
|
139
|
+
return 401, {"detail": _("Invalid token.")}
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
class PasswordChangeController(ControllerBase):
|
|
143
|
+
@http_post(
|
|
144
|
+
"/change_password",
|
|
145
|
+
permissions=[IsAuthenticated],
|
|
146
|
+
throttle=[UserRateThrottle()],
|
|
147
|
+
response=PasswordChangeResponseSchema,
|
|
148
|
+
)
|
|
149
|
+
@sensitive_post_parameters_m
|
|
150
|
+
@load_user
|
|
151
|
+
def change_user_password(self, request: HttpRequest, payload: PasswordChangeSchema):
|
|
152
|
+
return payload.get_response(request)
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
class TokenRefreshController(ControllerBase, CookieTokenMixin):
|
|
156
|
+
@http_post(
|
|
157
|
+
"/refresh",
|
|
158
|
+
auth=None,
|
|
159
|
+
response={200: TokenRefreshOutputSchema, 401: dict},
|
|
160
|
+
url_name="token_refresh",
|
|
161
|
+
operation_id="token_refresh",
|
|
162
|
+
throttle=[AnonRateThrottle()],
|
|
163
|
+
)
|
|
164
|
+
@get_user_agent
|
|
165
|
+
def refresh_token(self, request: HttpRequest, payload: Optional[TokenRefreshInputSchema]):
|
|
166
|
+
try:
|
|
167
|
+
input_data = {}
|
|
168
|
+
refresh_token = get_refresh_token(request)
|
|
169
|
+
|
|
170
|
+
if not refresh_token:
|
|
171
|
+
if payload.refresh is None:
|
|
172
|
+
raise InvalidToken
|
|
173
|
+
|
|
174
|
+
refresh_token = payload.refresh
|
|
175
|
+
|
|
176
|
+
input_data["refresh"] = refresh_token
|
|
177
|
+
ctx = user_agent_dict(request)
|
|
178
|
+
tokens = TokenRefreshSchema.model_validate(input_data, context=ctx)
|
|
179
|
+
|
|
180
|
+
self.set_token_cookie(access_token=tokens["access"], refresh_token=tokens["refresh"])
|
|
181
|
+
|
|
182
|
+
return tokens
|
|
183
|
+
except (TokenError, InvalidToken) as exc:
|
|
184
|
+
return 401, {"detail": _("Invalid token.")}
|
|
185
|
+
|
|
186
|
+
|
|
187
|
+
@api_controller(
|
|
188
|
+
"/auth",
|
|
189
|
+
)
|
|
190
|
+
class AuthController(
|
|
191
|
+
LoginController,
|
|
192
|
+
ReauthenticateController,
|
|
193
|
+
LogoutController,
|
|
194
|
+
PasswordChangeController,
|
|
195
|
+
TokenRefreshController,
|
|
196
|
+
EmailVerificationController,
|
|
197
|
+
RegisterController,
|
|
198
|
+
InvitationController,
|
|
199
|
+
PasswordResetConfirmController,
|
|
200
|
+
PasswordResetController,
|
|
201
|
+
ResetNewPasswordController,
|
|
202
|
+
):
|
|
203
|
+
pass
|