From 15c688074e5e4a96a130cf3d49f4b474edb45ad7 Mon Sep 17 00:00:00 2001 From: Matthias Mair Date: Fri, 19 Jan 2024 18:07:07 +0100 Subject: [PATCH 001/156] Remove django-allauth-2fa Fixes #6281 --- .../migrations/0011_auto_20240119_1659.py | 45 +++++++++++++++++++ requirements.in | 1 - requirements.txt | 7 --- 3 files changed, 45 insertions(+), 8 deletions(-) create mode 100644 InvenTree/users/migrations/0011_auto_20240119_1659.py diff --git a/InvenTree/users/migrations/0011_auto_20240119_1659.py b/InvenTree/users/migrations/0011_auto_20240119_1659.py new file mode 100644 index 000000000000..241ddd9ff4d9 --- /dev/null +++ b/InvenTree/users/migrations/0011_auto_20240119_1659.py @@ -0,0 +1,45 @@ +# Generated by Django 3.2.23 on 2024-01-19 16:59 + +import base64 + +from django.db import migrations + +from allauth.mfa.adapter import get_adapter +from allauth.mfa.models import Authenticator +from django_otp.plugins.otp_static.models import StaticDevice +from django_otp.plugins.otp_totp.models import TOTPDevice + + +def move_mfa(apps, schema_editor): + """Data migration to switch to django-allauth's new built-in MFA.""" + adapter = get_adapter() + authenticators = [] + for totp in TOTPDevice.objects.filter(confirmed=True).iterator(): + recovery_codes = set() + for sdevice in StaticDevice.objects.filter( + confirmed=True, user_id=totp.user_id + ).iterator(): + recovery_codes.update(sdevice.token_set.values_list('token', flat=True)) + secret = base64.b32encode(bytes.fromhex(totp.key)).decode('ascii') + totp_authenticator = Authenticator( + user_id=totp.user_id, + type=Authenticator.Type.TOTP, + data={'secret': adapter.encrypt(secret)}, + ) + authenticators.append(totp_authenticator) + authenticators.append( + Authenticator( + user_id=totp.user_id, + type=Authenticator.Type.RECOVERY_CODES, + data={'migrated_codes': [adapter.encrypt(c) for c in recovery_codes]}, + ) + ) + Authenticator.objects.bulk_create(authenticators) + + +class Migration(migrations.Migration): + dependencies = [('users', '0010_alter_apitoken_key')] + + operations = [ + migrations.RunPython(move_mfa, reverse_code=migrations.RunPython.noop) + ] diff --git a/requirements.in b/requirements.in index ce2a2a36bb79..c9d54024520b 100644 --- a/requirements.in +++ b/requirements.in @@ -3,7 +3,6 @@ Django>=3.2.14,<4 # Django package coreapi # API documentation for djangorestframework cryptography>=40.0.0,!=40.0.2 # Core cryptographic functionality django-allauth # SSO for external providers via OpenID -django-allauth-2fa # MFA / 2FA django-cleanup # Automated deletion of old / unused uploaded files django-cors-headers # CORS headers extension for DRF django-crispy-forms<2.0 # Form helpers # FIXED 2023-02-18 due to required updates in the new version diff --git a/requirements.txt b/requirements.txt index 22652b35c44d..a2722dbb6abb 100644 --- a/requirements.txt +++ b/requirements.txt @@ -64,7 +64,6 @@ django==3.2.23 # -r requirements.in # dj-rest-auth # django-allauth - # django-allauth-2fa # django-cors-headers # django-dbbackup # django-error-report-2 @@ -96,9 +95,6 @@ django==3.2.23 django-allauth==0.59.0 # via # -r requirements.in - # django-allauth-2fa -django-allauth-2fa==0.11.1 - # via -r requirements.in django-cleanup==8.0.0 # via -r requirements.in django-cors-headers==4.3.0 @@ -129,8 +125,6 @@ django-money==3.2.0 # via -r requirements.in django-mptt==0.11.0 # via -r requirements.in -django-otp==1.2.4 - # via django-allauth-2fa django-picklefield==3.1 # via django-q2 django-q-sentry==0.1.6 @@ -334,7 +328,6 @@ pyyaml==6.0.1 qrcode[pil]==7.4.2 # via # -r requirements.in - # django-allauth-2fa rapidfuzz==0.7.6 # via -r requirements.in redis==5.0.1 From cb05b201d99594f160e0db6a5a89b380306fe5ae Mon Sep 17 00:00:00 2001 From: Matthias Mair Date: Sun, 21 Jan 2024 15:16:46 +0100 Subject: [PATCH 002/156] fix req --- requirements.in | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.in b/requirements.in index c9d54024520b..6bb1f0878532 100644 --- a/requirements.in +++ b/requirements.in @@ -2,7 +2,7 @@ Django>=3.2.14,<4 # Django package coreapi # API documentation for djangorestframework cryptography>=40.0.0,!=40.0.2 # Core cryptographic functionality -django-allauth # SSO for external providers via OpenID +django-allauth[mfa] # SSO for external providers via OpenID django-cleanup # Automated deletion of old / unused uploaded files django-cors-headers # CORS headers extension for DRF django-crispy-forms<2.0 # Form helpers # FIXED 2023-02-18 due to required updates in the new version From 36da2736d1247c57ba75b8b76a0fba9bf3abd828 Mon Sep 17 00:00:00 2001 From: Matthias Mair Date: Sun, 21 Jan 2024 15:23:43 +0100 Subject: [PATCH 003/156] fix file again --- requirements.txt | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/requirements.txt b/requirements.txt index a2722dbb6abb..d7039db89336 100644 --- a/requirements.txt +++ b/requirements.txt @@ -76,7 +76,6 @@ django==3.2.23 # django-markdownify # django-money # django-mptt - # django-otp # django-picklefield # django-q2 # django-recurrence @@ -92,9 +91,8 @@ django==3.2.23 # djangorestframework # djangorestframework-simplejwt # drf-spectacular -django-allauth==0.59.0 - # via - # -r requirements.in +django-allauth[mfa]==0.60.1 + # via -r requirements.in django-cleanup==8.0.0 # via -r requirements.in django-cors-headers==4.3.0 @@ -328,6 +326,7 @@ pyyaml==6.0.1 qrcode[pil]==7.4.2 # via # -r requirements.in + # django-allauth rapidfuzz==0.7.6 # via -r requirements.in redis==5.0.1 From a8dfef5e52f6bb9f76c805ddbf7f9a6c97229cc0 Mon Sep 17 00:00:00 2001 From: Matthias Mair Date: Sun, 21 Jan 2024 15:24:43 +0100 Subject: [PATCH 004/156] remove allauth_2fa flows --- InvenTree/InvenTree/forms.py | 27 --------------------------- InvenTree/InvenTree/middleware.py | 30 +----------------------------- InvenTree/InvenTree/settings.py | 4 +--- InvenTree/InvenTree/urls.py | 1 - 4 files changed, 2 insertions(+), 60 deletions(-) diff --git a/InvenTree/InvenTree/forms.py b/InvenTree/InvenTree/forms.py index 6000a5e90691..b767f45c524f 100644 --- a/InvenTree/InvenTree/forms.py +++ b/InvenTree/InvenTree/forms.py @@ -1,7 +1,6 @@ """Helper forms which subclass Django forms to provide additional functionality.""" import logging -from urllib.parse import urlencode from django import forms from django.conf import settings @@ -13,10 +12,7 @@ from allauth.account.adapter import DefaultAccountAdapter from allauth.account.forms import LoginForm, SignupForm, set_form_field_order -from allauth.core.exceptions import ImmediateHttpResponse from allauth.socialaccount.adapter import DefaultSocialAccountAdapter -from allauth_2fa.adapter import OTPAdapter -from allauth_2fa.utils import user_has_valid_totp_device from crispy_forms.bootstrap import AppendedText, PrependedAppendedText, PrependedText from crispy_forms.helper import FormHelper from crispy_forms.layout import Field, Layout @@ -336,29 +332,6 @@ def is_auto_signup_allowed(self, request, sociallogin): return super().is_auto_signup_allowed(request, sociallogin) return False - # from OTPAdapter - def has_2fa_enabled(self, user): - """Returns True if the user has 2FA configured.""" - return user_has_valid_totp_device(user) - - def login(self, request, user): - """Ensure user is send to 2FA before login if enabled.""" - # Require two-factor authentication if it has been configured. - if self.has_2fa_enabled(user): - # Cast to string for the case when this is not a JSON serializable - # object, e.g. a UUID. - request.session['allauth_2fa_user_id'] = str(user.id) - - redirect_url = reverse('two-factor-authenticate') - # Add GET parameters to the URL if they exist. - if request.GET: - redirect_url += '?' + urlencode(request.GET) - - raise ImmediateHttpResponse(response=HttpResponseRedirect(redirect_url)) - - # Otherwise defer to the original allauth adapter. - return super().login(request, user) - def authentication_error( self, request, provider_id, error=None, exception=None, extra_context=None ): diff --git a/InvenTree/InvenTree/middleware.py b/InvenTree/InvenTree/middleware.py index 79f51b79145f..05f7f64667db 100644 --- a/InvenTree/InvenTree/middleware.py +++ b/InvenTree/InvenTree/middleware.py @@ -7,9 +7,8 @@ from django.contrib.auth.middleware import PersistentRemoteUserMiddleware from django.http import HttpResponse from django.shortcuts import redirect -from django.urls import Resolver404, include, path, resolve, reverse_lazy +from django.urls import include, path, resolve, reverse_lazy -from allauth_2fa.middleware import AllauthTwoFactorMiddleware, BaseRequire2FAMiddleware from error_report.middleware import ExceptionProcessor from InvenTree.urls import frontendpatterns @@ -127,33 +126,6 @@ def __call__(self, request): url_matcher = path('', include(frontendpatterns)) -class Check2FAMiddleware(BaseRequire2FAMiddleware): - """Check if user is required to have MFA enabled.""" - - def require_2fa(self, request): - """Use setting to check if MFA should be enforced for frontend page.""" - from common.models import InvenTreeSetting - - try: - if url_matcher.resolve(request.path[1:]): - return InvenTreeSetting.get_setting('LOGIN_ENFORCE_MFA') - except Resolver404: - pass - return False - - -class CustomAllauthTwoFactorMiddleware(AllauthTwoFactorMiddleware): - """This function ensures only frontend code triggers the MFA auth cycle.""" - - def process_request(self, request): - """Check if requested url is forntend and enforce MFA check.""" - try: - if not url_matcher.resolve(request.path[1:]): - super().process_request(request) - except Resolver404: - pass - - class InvenTreeRemoteUserMiddleware(PersistentRemoteUserMiddleware): """Middleware to check if HTTP-header based auth is enabled and to set it up.""" diff --git a/InvenTree/InvenTree/settings.py b/InvenTree/InvenTree/settings.py index f16ebaf6debe..cb914cfd22ad 100644 --- a/InvenTree/InvenTree/settings.py +++ b/InvenTree/InvenTree/settings.py @@ -249,7 +249,7 @@ 'django_otp', # OTP is needed for MFA - base package 'django_otp.plugins.otp_totp', # Time based OTP 'django_otp.plugins.otp_static', # Backup codes - 'allauth_2fa', # MFA flow for allauth + 'allauth.mfa', # MFA for for allauth 'dj_rest_auth', # Authentication APIs - dj-rest-auth 'dj_rest_auth.registration', # Registration APIs - dj-rest-auth' 'drf_spectacular', # API documentation @@ -269,12 +269,10 @@ 'django.contrib.auth.middleware.AuthenticationMiddleware', 'InvenTree.middleware.InvenTreeRemoteUserMiddleware', # Remote / proxy auth 'django_otp.middleware.OTPMiddleware', # MFA support - 'InvenTree.middleware.CustomAllauthTwoFactorMiddleware', # Flow control for allauth 'allauth.account.middleware.AccountMiddleware', 'django.contrib.messages.middleware.MessageMiddleware', 'django.middleware.clickjacking.XFrameOptionsMiddleware', 'InvenTree.middleware.AuthRequiredMiddleware', - 'InvenTree.middleware.Check2FAMiddleware', # Check if the user should be forced to use MFA 'maintenance_mode.middleware.MaintenanceModeMiddleware', 'InvenTree.middleware.InvenTreeExceptionProcessor', # Error reporting ], diff --git a/InvenTree/InvenTree/urls.py b/InvenTree/InvenTree/urls.py index 547764cf5d30..5111e4c67cd8 100644 --- a/InvenTree/InvenTree/urls.py +++ b/InvenTree/InvenTree/urls.py @@ -405,7 +405,6 @@ ), # Override login page path('accounts/login/', CustomLoginView.as_view(), name='account_login'), - path('accounts/', include('allauth_2fa.urls')), # MFA support path('accounts/', include('allauth.urls')), # included urlpatterns ] From 94473f3d41c26a2eaf340e184063b022753b5605 Mon Sep 17 00:00:00 2001 From: Matthias Mair Date: Sun, 21 Jan 2024 15:36:14 +0100 Subject: [PATCH 005/156] reintroduce otp --- requirements.in | 1 + requirements.txt | 2 ++ 2 files changed, 3 insertions(+) diff --git a/requirements.in b/requirements.in index 6bb1f0878532..1014d91b9c84 100644 --- a/requirements.in +++ b/requirements.in @@ -25,6 +25,7 @@ django-sql-utils # Advanced query annotation / aggregatio django-sslserver # Secure HTTP development server django-stdimage # Advanced ImageField management django-taggit # Tagging support +django-otp==1.2.4 # Two-factor authentication (legacy to ensure migrations) https://github.com/inventree/InvenTree/pull/6293 django-user-sessions # user sessions in DB django-weasyprint # django weasyprint integration djangorestframework # DRF framework diff --git a/requirements.txt b/requirements.txt index d7039db89336..161c26cc3932 100644 --- a/requirements.txt +++ b/requirements.txt @@ -123,6 +123,8 @@ django-money==3.2.0 # via -r requirements.in django-mptt==0.11.0 # via -r requirements.in +django-otp==1.2.4 + # via -r requirements.in django-picklefield==3.1 # via django-q2 django-q-sentry==0.1.6 From 068c05a6f08249d05cac71e8ee08e5e13932a5cd Mon Sep 17 00:00:00 2001 From: Matthias Mair Date: Sun, 21 Jan 2024 15:39:40 +0100 Subject: [PATCH 006/156] fix rq --- requirements.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/requirements.txt b/requirements.txt index 161c26cc3932..332b2a6bf9e6 100644 --- a/requirements.txt +++ b/requirements.txt @@ -76,6 +76,7 @@ django==3.2.23 # django-markdownify # django-money # django-mptt + # django-otp # django-picklefield # django-q2 # django-recurrence From c31e8fad30562a37f6167522527716845c1c496d Mon Sep 17 00:00:00 2001 From: Matthias Mair Date: Sun, 21 Jan 2024 15:43:46 +0100 Subject: [PATCH 007/156] remove old ref --- InvenTree/InvenTree/forms.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/InvenTree/InvenTree/forms.py b/InvenTree/InvenTree/forms.py index 01acaac65222..edeec8cb0196 100644 --- a/InvenTree/InvenTree/forms.py +++ b/InvenTree/InvenTree/forms.py @@ -292,9 +292,7 @@ def get_email_confirmation_url(self, request, emailconfirmation): return Site.objects.get_current().domain + url -class CustomAccountAdapter( - CustomUrlMixin, RegistratonMixin, OTPAdapter, DefaultAccountAdapter -): +class CustomAccountAdapter(CustomUrlMixin, RegistratonMixin, DefaultAccountAdapter): """Override of adapter to use dynamic settings.""" def send_mail(self, template_prefix, email, context): From 027008262aecf67976f8a9851c1cdd8e75dea94e Mon Sep 17 00:00:00 2001 From: Matthias Mair Date: Sun, 21 Jan 2024 15:53:42 +0100 Subject: [PATCH 008/156] remove otp things from settings --- InvenTree/InvenTree/settings.py | 6 ------ 1 file changed, 6 deletions(-) diff --git a/InvenTree/InvenTree/settings.py b/InvenTree/InvenTree/settings.py index cb914cfd22ad..619804987179 100644 --- a/InvenTree/InvenTree/settings.py +++ b/InvenTree/InvenTree/settings.py @@ -246,9 +246,6 @@ 'allauth', # Base app for SSO 'allauth.account', # Extend user with accounts 'allauth.socialaccount', # Use 'social' providers - 'django_otp', # OTP is needed for MFA - base package - 'django_otp.plugins.otp_totp', # Time based OTP - 'django_otp.plugins.otp_static', # Backup codes 'allauth.mfa', # MFA for for allauth 'dj_rest_auth', # Authentication APIs - dj-rest-auth 'dj_rest_auth.registration', # Registration APIs - dj-rest-auth' @@ -268,7 +265,6 @@ 'corsheaders.middleware.CorsMiddleware', 'django.contrib.auth.middleware.AuthenticationMiddleware', 'InvenTree.middleware.InvenTreeRemoteUserMiddleware', # Remote / proxy auth - 'django_otp.middleware.OTPMiddleware', # MFA support 'allauth.account.middleware.AccountMiddleware', 'django.contrib.messages.middleware.MessageMiddleware', 'django.middleware.clickjacking.XFrameOptionsMiddleware', @@ -1006,8 +1002,6 @@ ) ACCOUNT_LOGOUT_ON_PASSWORD_CHANGE = True ACCOUNT_PREVENT_ENUMERATION = True -# 2FA -REMOVE_SUCCESS_URL = 'settings' # override forms / adapters ACCOUNT_FORMS = { From 6037a702856b84a09b9d97dc7c0a2bdcf169e2a9 Mon Sep 17 00:00:00 2001 From: Matthias Mair Date: Thu, 21 Mar 2024 23:49:43 +0100 Subject: [PATCH 009/156] reintroduce otp codes --- InvenTree/InvenTree/settings.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/InvenTree/InvenTree/settings.py b/InvenTree/InvenTree/settings.py index 7884206d2fbf..16fd04bd11dc 100644 --- a/InvenTree/InvenTree/settings.py +++ b/InvenTree/InvenTree/settings.py @@ -234,6 +234,9 @@ 'allauth.account', # Extend user with accounts 'allauth.socialaccount', # Use 'social' providers 'allauth.mfa', # MFA for for allauth + 'django_otp', # OTP is needed for MFA - base package + 'django_otp.plugins.otp_totp', # Time based OTP + 'django_otp.plugins.otp_static', # Backup codes 'dj_rest_auth', # Authentication APIs - dj-rest-auth 'dj_rest_auth.registration', # Registration APIs - dj-rest-auth' 'drf_spectacular', # API documentation From 1f5091d0905e24a61f001702821bad3f6bb1e4e5 Mon Sep 17 00:00:00 2001 From: Matthias Mair Date: Fri, 22 Mar 2024 00:27:09 +0100 Subject: [PATCH 010/156] remove totp section --- InvenTree/templates/InvenTree/settings/user.html | 4 ---- 1 file changed, 4 deletions(-) diff --git a/InvenTree/templates/InvenTree/settings/user.html b/InvenTree/templates/InvenTree/settings/user.html index f164d2aa977b..8c0c2437600c 100644 --- a/InvenTree/templates/InvenTree/settings/user.html +++ b/InvenTree/templates/InvenTree/settings/user.html @@ -155,10 +155,6 @@

{% trans "Multifactor" %}

{% trans "Change factors" %}
- {% trans "Setup multifactor" %} - {% if user.staticdevice_set.all or user.totpdevice_set.all %} - {% trans "Remove multifactor" %} - {% endif %}
From 20dda94a2ffd8e88dd6e0ddbeca06445e8af67d1 Mon Sep 17 00:00:00 2001 From: Matthias Mair Date: Wed, 27 Mar 2024 10:45:12 +0100 Subject: [PATCH 011/156] bump version --- requirements.in | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.in b/requirements.in index 03e2bb098a7e..169003be0656 100644 --- a/requirements.in +++ b/requirements.in @@ -27,7 +27,7 @@ django-sql-utils # Advanced query annotation / aggregatio django-sslserver # Secure HTTP development server django-stdimage # Advanced ImageField management django-taggit # Tagging support -django-otp==1.2.4 # Two-factor authentication (legacy to ensure migrations) https://github.com/inventree/InvenTree/pull/6293 +django-otp==1.3.0 # Two-factor authentication (legacy to ensure migrations) https://github.com/inventree/InvenTree/pull/6293 django-user-sessions # user sessions in DB django-weasyprint # django weasyprint integration djangorestframework # DRF framework From e0f70d5eb5708055696c57cc748f0a34f86d9074 Mon Sep 17 00:00:00 2001 From: Matthias Mair Date: Wed, 27 Mar 2024 16:47:07 +0100 Subject: [PATCH 012/156] fix reqs --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index ab13cdf3a80f..0e24404d01f6 100644 --- a/requirements.txt +++ b/requirements.txt @@ -95,7 +95,7 @@ django-maintenance-mode==0.21.1 django-markdownify==0.9.3 django-money==3.2.0 django-mptt==0.16.0 -django-otp==1.2.4 +django-otp==1.3.0 django-picklefield==3.1 # via django-q2 django-q-sentry==0.1.6 From 3d30082df9ea4885a17d524b8a64768e6221f0fb Mon Sep 17 00:00:00 2001 From: Matthias Mair Date: Wed, 27 Mar 2024 16:50:05 +0100 Subject: [PATCH 013/156] add missing model --- InvenTree/users/models.py | 1 + 1 file changed, 1 insertion(+) diff --git a/InvenTree/users/models.py b/InvenTree/users/models.py index e2ddcd34179d..ae05be998c63 100644 --- a/InvenTree/users/models.py +++ b/InvenTree/users/models.py @@ -226,6 +226,7 @@ def get_ruleset_models(): 'otp_totp_totpdevice', 'otp_static_statictoken', 'otp_static_staticdevice', + 'mfa_authenticator', 'plugin_pluginconfig', 'plugin_pluginsetting', 'plugin_notificationusersetting', From e7b7d03d141c019ba782a6abd7a4824c8af7eccc Mon Sep 17 00:00:00 2001 From: Matthias Mair Date: Mon, 8 Apr 2024 20:40:39 +0200 Subject: [PATCH 014/156] ignore TOTP migration if the model is not laoded --- .../users/migrations/0011_auto_20240119_1659.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/backend/InvenTree/users/migrations/0011_auto_20240119_1659.py b/src/backend/InvenTree/users/migrations/0011_auto_20240119_1659.py index 241ddd9ff4d9..fe0a0a12b512 100644 --- a/src/backend/InvenTree/users/migrations/0011_auto_20240119_1659.py +++ b/src/backend/InvenTree/users/migrations/0011_auto_20240119_1659.py @@ -2,7 +2,7 @@ import base64 -from django.db import migrations +from django.db import OperationalError, migrations from allauth.mfa.adapter import get_adapter from allauth.mfa.models import Authenticator @@ -13,6 +13,13 @@ def move_mfa(apps, schema_editor): """Data migration to switch to django-allauth's new built-in MFA.""" adapter = get_adapter() + try: + TOTPDevice.objects.all().count() + except OperationalError: + # The table may not exist + print('Skipping totp migration as model does not exist') + return + authenticators = [] for totp in TOTPDevice.objects.filter(confirmed=True).iterator(): recovery_codes = set() From 81c03464e5fe08a09f1a8f5fac2c94a1a5d2681d Mon Sep 17 00:00:00 2001 From: Matthias Mair Date: Mon, 8 Apr 2024 21:09:51 +0200 Subject: [PATCH 015/156] add model deps --- .../InvenTree/users/migrations/0011_auto_20240119_1659.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/backend/InvenTree/users/migrations/0011_auto_20240119_1659.py b/src/backend/InvenTree/users/migrations/0011_auto_20240119_1659.py index fe0a0a12b512..9e8d71ccd2e8 100644 --- a/src/backend/InvenTree/users/migrations/0011_auto_20240119_1659.py +++ b/src/backend/InvenTree/users/migrations/0011_auto_20240119_1659.py @@ -45,7 +45,7 @@ def move_mfa(apps, schema_editor): class Migration(migrations.Migration): - dependencies = [('users', '0010_alter_apitoken_key')] + dependencies = [('users', '0010_alter_apitoken_key'), ('otp_static', '0002_throttling'), ('otp_totp', '0002_auto_20190420_0723')] operations = [ migrations.RunPython(move_mfa, reverse_code=migrations.RunPython.noop) From 59390aeaebde35b6e639741e805cc56f1cab9b07 Mon Sep 17 00:00:00 2001 From: Matthias Mair Date: Mon, 8 Apr 2024 21:28:13 +0200 Subject: [PATCH 016/156] add extra migrations step for easier testing --- .../migrations/0011_auto_20240119_1659.py | 54 ++++--------------- .../0012_migrate_mfa_20240408_1659.py | 52 ++++++++++++++++++ 2 files changed, 62 insertions(+), 44 deletions(-) create mode 100644 src/backend/InvenTree/users/migrations/0012_migrate_mfa_20240408_1659.py diff --git a/src/backend/InvenTree/users/migrations/0011_auto_20240119_1659.py b/src/backend/InvenTree/users/migrations/0011_auto_20240119_1659.py index 9e8d71ccd2e8..df72a83dd4c5 100644 --- a/src/backend/InvenTree/users/migrations/0011_auto_20240119_1659.py +++ b/src/backend/InvenTree/users/migrations/0011_auto_20240119_1659.py @@ -1,52 +1,18 @@ # Generated by Django 3.2.23 on 2024-01-19 16:59 -import base64 - -from django.db import OperationalError, migrations - -from allauth.mfa.adapter import get_adapter -from allauth.mfa.models import Authenticator -from django_otp.plugins.otp_static.models import StaticDevice -from django_otp.plugins.otp_totp.models import TOTPDevice - - -def move_mfa(apps, schema_editor): - """Data migration to switch to django-allauth's new built-in MFA.""" - adapter = get_adapter() - try: - TOTPDevice.objects.all().count() - except OperationalError: - # The table may not exist - print('Skipping totp migration as model does not exist') - return - - authenticators = [] - for totp in TOTPDevice.objects.filter(confirmed=True).iterator(): - recovery_codes = set() - for sdevice in StaticDevice.objects.filter( - confirmed=True, user_id=totp.user_id - ).iterator(): - recovery_codes.update(sdevice.token_set.values_list('token', flat=True)) - secret = base64.b32encode(bytes.fromhex(totp.key)).decode('ascii') - totp_authenticator = Authenticator( - user_id=totp.user_id, - type=Authenticator.Type.TOTP, - data={'secret': adapter.encrypt(secret)}, - ) - authenticators.append(totp_authenticator) - authenticators.append( - Authenticator( - user_id=totp.user_id, - type=Authenticator.Type.RECOVERY_CODES, - data={'migrated_codes': [adapter.encrypt(c) for c in recovery_codes]}, - ) - ) - Authenticator.objects.bulk_create(authenticators) +from django.db import migrations class Migration(migrations.Migration): - dependencies = [('users', '0010_alter_apitoken_key'), ('otp_static', '0002_throttling'), ('otp_totp', '0002_auto_20190420_0723')] + dependencies = [ + ('users', '0010_alter_apitoken_key'), + ('otp_static', '0002_throttling'), + ('otp_totp', '0002_auto_20190420_0723'), + ('mfa', '0002_authenticator_timestamps'), + ] operations = [ - migrations.RunPython(move_mfa, reverse_code=migrations.RunPython.noop) + migrations.RunPython( + migrations.RunPython.noop, reverse_code=migrations.RunPython.noop + ) ] diff --git a/src/backend/InvenTree/users/migrations/0012_migrate_mfa_20240408_1659.py b/src/backend/InvenTree/users/migrations/0012_migrate_mfa_20240408_1659.py new file mode 100644 index 000000000000..f6dbb12a0797 --- /dev/null +++ b/src/backend/InvenTree/users/migrations/0012_migrate_mfa_20240408_1659.py @@ -0,0 +1,52 @@ +# Generated by Django 3.2.23 on 2024-01-19 16:59 + +import base64 + +from django.db import OperationalError, migrations + +from allauth.mfa.adapter import get_adapter +from allauth.mfa.models import Authenticator +from django_otp.plugins.otp_static.models import StaticDevice +from django_otp.plugins.otp_totp.models import TOTPDevice + + +def move_mfa(apps, schema_editor): + """Data migration to switch to django-allauth's new built-in MFA.""" + adapter = get_adapter() + try: + TOTPDevice.objects.all().count() + except OperationalError: + # The table may not exist + print('Skipping totp migration as model does not exist') + return + + authenticators = [] + for totp in TOTPDevice.objects.filter(confirmed=True).iterator(): + recovery_codes = set() + for sdevice in StaticDevice.objects.filter( + confirmed=True, user_id=totp.user_id + ).iterator(): + recovery_codes.update(sdevice.token_set.values_list('token', flat=True)) + secret = base64.b32encode(bytes.fromhex(totp.key)).decode('ascii') + totp_authenticator = Authenticator( + user_id=totp.user_id, + type=Authenticator.Type.TOTP, + data={'secret': adapter.encrypt(secret)}, + ) + authenticators.append(totp_authenticator) + authenticators.append( + Authenticator( + user_id=totp.user_id, + type=Authenticator.Type.RECOVERY_CODES, + data={'migrated_codes': [adapter.encrypt(c) for c in recovery_codes]}, + ) + ) + Authenticator.objects.bulk_create(authenticators) + + +class Migration(migrations.Migration): + dependencies = [('users', '0011_auto_20240119_1659')] + + operations = [ + migrations.RunPython(move_mfa, reverse_code=migrations.RunPython.noop) + ] From 08bb8510fea27bf80fd7bae2bb9cd9b1c3dcc8d1 Mon Sep 17 00:00:00 2001 From: Matthias Mair Date: Mon, 8 Apr 2024 21:28:32 +0200 Subject: [PATCH 017/156] add migration testing --- .../InvenTree/users/test_migrations.py | 35 +++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/src/backend/InvenTree/users/test_migrations.py b/src/backend/InvenTree/users/test_migrations.py index 9843b7917717..9d9c54e5b9f6 100644 --- a/src/backend/InvenTree/users/test_migrations.py +++ b/src/backend/InvenTree/users/test_migrations.py @@ -24,3 +24,38 @@ def test_users_exist(self): User = self.new_state.apps.get_model('auth', 'user') self.assertEqual(User.objects.count(), 2) + + +class MFAMigrations(MigratorTestCase): + """Test entire schema migration sequence for the users app.""" + + migrate_from = ('users', '0011_auto_20240119_1659') + migrate_to = ('users', '0012_migrate_mfa_20240408_1659') + + def prepare(self): + """Setup the initial state of the database before migrations.""" + User = self.old_state.apps.get_model('auth', 'user') + TOTPDevice = self.old_state.apps.get_model('otp_totp', 'TOTPDevice') + + abc = User.objects.create( + username='fred', email='fred@fred.com', password='password' + ) + TOTPDevice.objects.create( + user=abc, confirmed=True, key='3132333435363738393031323334353637383930' + ) + abc1 = User.objects.create( + username='brad', email='brad@fred.com', password='password' + ) + TOTPDevice.objects.create( + user=abc1, confirmed=False, key='3132333435363738393031323334353637383930' + ) + + def test_users_exist(self): + """Test that users exist in the database.""" + User = self.new_state.apps.get_model('auth', 'user') + Authenticator = self.new_state.apps.get_model('mfa', 'Authenticator') + + self.assertEqual(User.objects.count(), 2) + # 2 Tokens - both for user 1 + self.assertEqual(Authenticator.objects.count(), 2) + self.assertEqual([1, 1], [i.user_id for i in Authenticator.objects.all()]) From 432dbf78a89cd82aeff40af28c24afcef29b8790 Mon Sep 17 00:00:00 2001 From: Matthias Mair Date: Mon, 8 Apr 2024 21:31:55 +0200 Subject: [PATCH 018/156] remove old catch --- .../users/migrations/0012_migrate_mfa_20240408_1659.py | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/src/backend/InvenTree/users/migrations/0012_migrate_mfa_20240408_1659.py b/src/backend/InvenTree/users/migrations/0012_migrate_mfa_20240408_1659.py index f6dbb12a0797..f126960ab77d 100644 --- a/src/backend/InvenTree/users/migrations/0012_migrate_mfa_20240408_1659.py +++ b/src/backend/InvenTree/users/migrations/0012_migrate_mfa_20240408_1659.py @@ -2,7 +2,7 @@ import base64 -from django.db import OperationalError, migrations +from django.db import migrations from allauth.mfa.adapter import get_adapter from allauth.mfa.models import Authenticator @@ -13,13 +13,6 @@ def move_mfa(apps, schema_editor): """Data migration to switch to django-allauth's new built-in MFA.""" adapter = get_adapter() - try: - TOTPDevice.objects.all().count() - except OperationalError: - # The table may not exist - print('Skipping totp migration as model does not exist') - return - authenticators = [] for totp in TOTPDevice.objects.filter(confirmed=True).iterator(): recovery_codes = set() From 5b619562996b4ccda6676ea15aa99384249ac91c Mon Sep 17 00:00:00 2001 From: Matthias Mair Date: Tue, 9 Apr 2024 00:08:13 +0200 Subject: [PATCH 019/156] cover static devies too --- src/backend/InvenTree/users/test_migrations.py | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/src/backend/InvenTree/users/test_migrations.py b/src/backend/InvenTree/users/test_migrations.py index 9d9c54e5b9f6..d6487a4ef0dd 100644 --- a/src/backend/InvenTree/users/test_migrations.py +++ b/src/backend/InvenTree/users/test_migrations.py @@ -36,19 +36,17 @@ def prepare(self): """Setup the initial state of the database before migrations.""" User = self.old_state.apps.get_model('auth', 'user') TOTPDevice = self.old_state.apps.get_model('otp_totp', 'TOTPDevice') + StaticDevice = self.old_state.apps.get_model('otp_static', 'StaticDevice') abc = User.objects.create( username='fred', email='fred@fred.com', password='password' ) - TOTPDevice.objects.create( - user=abc, confirmed=True, key='3132333435363738393031323334353637383930' - ) + TOTPDevice.objects.create(user=abc, confirmed=True, key='1234') abc1 = User.objects.create( username='brad', email='brad@fred.com', password='password' ) - TOTPDevice.objects.create( - user=abc1, confirmed=False, key='3132333435363738393031323334353637383930' - ) + TOTPDevice.objects.create(user=abc1, confirmed=False, key='1234') + StaticDevice.objects.create(user=abc1, confirmed=True) def test_users_exist(self): """Test that users exist in the database.""" From be183791cd4e2aa0130d709d4fabe9d14858d6c3 Mon Sep 17 00:00:00 2001 From: Matthias Mair Date: Tue, 25 Jun 2024 13:51:01 +0200 Subject: [PATCH 020/156] remove more old stuff --- src/backend/InvenTree/InvenTree/forms.py | 20 -- src/backend/InvenTree/InvenTree/settings.py | 29 -- .../InvenTree/InvenTree/social_auth_urls.py | 267 ------------------ src/backend/InvenTree/InvenTree/sso.py | 9 - src/backend/InvenTree/InvenTree/urls.py | 60 ---- src/backend/InvenTree/InvenTree/views.py | 52 ---- src/backend/InvenTree/users/api.py | 70 +---- src/backend/requirements.in | 1 - src/backend/requirements.txt | 4 - 9 files changed, 3 insertions(+), 509 deletions(-) delete mode 100644 src/backend/InvenTree/InvenTree/social_auth_urls.py diff --git a/src/backend/InvenTree/InvenTree/forms.py b/src/backend/InvenTree/InvenTree/forms.py index d2d2278f642c..4d6de19d5937 100644 --- a/src/backend/InvenTree/InvenTree/forms.py +++ b/src/backend/InvenTree/InvenTree/forms.py @@ -15,8 +15,6 @@ from crispy_forms.bootstrap import AppendedText, PrependedAppendedText, PrependedText from crispy_forms.helper import FormHelper from crispy_forms.layout import Field, Layout -from dj_rest_auth.registration.serializers import RegisterSerializer -from rest_framework import serializers import InvenTree.helpers_model import InvenTree.sso @@ -341,21 +339,3 @@ def authentication_error( # Log the error to the database log_error(path, error_name=error, error_data=exception) logger.error("SSO error for provider '%s' - check admin error log", provider_id) - - -# override dj-rest-auth -class CustomRegisterSerializer(RegisterSerializer): - """Override of serializer to use dynamic settings.""" - - email = serializers.EmailField() - - def __init__(self, instance=None, data=..., **kwargs): - """Check settings to influence which fields are needed.""" - kwargs['email_required'] = get_global_setting('LOGIN_MAIL_REQUIRED') - super().__init__(instance, data, **kwargs) - - def save(self, request): - """Override to check if registration is open.""" - if registration_enabled(): - return super().save(request) - raise forms.ValidationError(_('Registration is disabled.')) diff --git a/src/backend/InvenTree/InvenTree/settings.py b/src/backend/InvenTree/InvenTree/settings.py index cf10fa394d8c..6fe0448e3154 100644 --- a/src/backend/InvenTree/InvenTree/settings.py +++ b/src/backend/InvenTree/InvenTree/settings.py @@ -235,8 +235,6 @@ 'django_otp', # OTP is needed for MFA - base package 'django_otp.plugins.otp_totp', # Time based OTP 'django_otp.plugins.otp_static', # Backup codes - 'dj_rest_auth', # Authentication APIs - dj-rest-auth - 'dj_rest_auth.registration', # Registration APIs - dj-rest-auth' 'drf_spectacular', # API documentation 'django_ical', # For exporting calendars ] @@ -484,33 +482,6 @@ 'rest_framework.renderers.BrowsableAPIRenderer' ) -# JWT switch -USE_JWT = get_boolean_setting('INVENTREE_USE_JWT', 'use_jwt', False) -REST_USE_JWT = USE_JWT - -# dj-rest-auth -REST_AUTH = { - 'SESSION_LOGIN': True, - 'TOKEN_MODEL': 'users.models.ApiToken', - 'TOKEN_CREATOR': 'users.models.default_create_token', - 'USE_JWT': USE_JWT, -} - -OLD_PASSWORD_FIELD_ENABLED = True -REST_AUTH_REGISTER_SERIALIZERS = { - 'REGISTER_SERIALIZER': 'InvenTree.forms.CustomRegisterSerializer' -} - -# JWT settings - rest_framework_simplejwt -if USE_JWT: - JWT_AUTH_COOKIE = 'inventree-auth' - JWT_AUTH_REFRESH_COOKIE = 'inventree-token' - REST_FRAMEWORK['DEFAULT_AUTHENTICATION_CLASSES'].append( - 'dj_rest_auth.jwt_auth.JWTCookieAuthentication' - ) - INSTALLED_APPS.append('rest_framework_simplejwt') - - # WSGI default setting WSGI_APPLICATION = 'InvenTree.wsgi.application' diff --git a/src/backend/InvenTree/InvenTree/social_auth_urls.py b/src/backend/InvenTree/InvenTree/social_auth_urls.py deleted file mode 100644 index 49d9e461ee40..000000000000 --- a/src/backend/InvenTree/InvenTree/social_auth_urls.py +++ /dev/null @@ -1,267 +0,0 @@ -"""API endpoints for social authentication with allauth.""" - -import logging -from importlib import import_module - -from django.urls import NoReverseMatch, include, path, reverse - -from allauth.account.models import EmailAddress -from allauth.socialaccount import providers -from allauth.socialaccount.providers.oauth2.views import OAuth2Adapter, OAuth2LoginView -from drf_spectacular.utils import OpenApiResponse, extend_schema -from rest_framework import serializers -from rest_framework.exceptions import NotFound -from rest_framework.permissions import AllowAny, IsAuthenticated -from rest_framework.response import Response - -import InvenTree.sso -from common.settings import get_global_setting -from InvenTree.mixins import CreateAPI, ListAPI, ListCreateAPI -from InvenTree.serializers import EmptySerializer, InvenTreeModelSerializer - -logger = logging.getLogger('inventree') - - -class GenericOAuth2ApiLoginView(OAuth2LoginView): - """Api view to login a user with a social account.""" - - def dispatch(self, request, *args, **kwargs): - """Dispatch the regular login view directly.""" - return self.login(request, *args, **kwargs) - - -class GenericOAuth2ApiConnectView(GenericOAuth2ApiLoginView): - """Api view to connect a social account to the current user.""" - - def dispatch(self, request, *args, **kwargs): - """Dispatch the connect request directly.""" - # Override the request method be in connection mode - request.GET = request.GET.copy() - request.GET['process'] = 'connect' - - # Resume the dispatch - return super().dispatch(request, *args, **kwargs) - - -def handle_oauth2(adapter: OAuth2Adapter): - """Define urls for oauth2 endpoints.""" - return [ - path( - 'login/', - GenericOAuth2ApiLoginView.adapter_view(adapter), - name=f'{provider.id}_api_login', - ), - path( - 'connect/', - GenericOAuth2ApiConnectView.adapter_view(adapter), - name=f'{provider.id}_api_connect', - ), - ] - - -legacy = { - 'twitter': 'twitter_oauth2', - 'bitbucket': 'bitbucket_oauth2', - 'linkedin': 'linkedin_oauth2', - 'vimeo': 'vimeo_oauth2', - 'openid': 'openid_connect', -} # legacy connectors - - -# Collect urls for all loaded providers -social_auth_urlpatterns = [] - -provider_urlpatterns = [] - -for name, provider in providers.registry.provider_map.items(): - try: - prov_mod = import_module(provider.get_package() + '.views') - except ImportError: - logger.exception('Could not import authentication provider %s', name) - continue - - # Try to extract the adapter class - adapters = [ - cls - for cls in prov_mod.__dict__.values() - if isinstance(cls, type) - and cls != OAuth2Adapter - and issubclass(cls, OAuth2Adapter) - ] - - # Get urls - urls = [] - if len(adapters) == 1: - urls = handle_oauth2(adapter=adapters[0]) - else: - if provider.id in legacy: - logger.warning( - '`%s` is not supported on platform UI. Use `%s` instead.', - provider.id, - legacy[provider.id], - ) - continue - else: - logger.error( - 'Found handler that is not yet ready for platform UI: `%s`. Open an feature request on GitHub if you need it implemented.', - provider.id, - ) - continue - provider_urlpatterns += [path(f'{provider.id}/', include(urls))] - - -social_auth_urlpatterns += provider_urlpatterns - - -class SocialProviderListResponseSerializer(serializers.Serializer): - """Serializer for the SocialProviderListView.""" - - class SocialProvider(serializers.Serializer): - """Serializer for the SocialProviderListResponseSerializer.""" - - id = serializers.CharField() - name = serializers.CharField() - configured = serializers.BooleanField() - login = serializers.URLField() - connect = serializers.URLField() - display_name = serializers.CharField() - - sso_enabled = serializers.BooleanField() - sso_registration = serializers.BooleanField() - mfa_required = serializers.BooleanField() - providers = SocialProvider(many=True) - registration_enabled = serializers.BooleanField() - password_forgotten_enabled = serializers.BooleanField() - - -class SocialProviderListView(ListAPI): - """List of available social providers.""" - - permission_classes = (AllowAny,) - serializer_class = EmptySerializer - - @extend_schema( - responses={200: OpenApiResponse(response=SocialProviderListResponseSerializer)} - ) - def get(self, request, *args, **kwargs): - """Get the list of providers.""" - provider_list = [] - for provider in providers.registry.provider_map.values(): - provider_data = { - 'id': provider.id, - 'name': provider.name, - 'configured': False, - } - - try: - provider_data['login'] = request.build_absolute_uri( - reverse(f'{provider.id}_api_login') - ) - except NoReverseMatch: - provider_data['login'] = None - - try: - provider_data['connect'] = request.build_absolute_uri( - reverse(f'{provider.id}_api_connect') - ) - except NoReverseMatch: - provider_data['connect'] = None - - provider_data['configured'] = InvenTree.sso.check_provider(provider) - provider_data['display_name'] = InvenTree.sso.provider_display_name( - provider - ) - - provider_list.append(provider_data) - - data = { - 'sso_enabled': InvenTree.sso.login_enabled(), - 'sso_registration': InvenTree.sso.registration_enabled(), - 'mfa_required': get_global_setting('LOGIN_ENFORCE_MFA'), - 'providers': provider_list, - 'registration_enabled': get_global_setting('LOGIN_ENABLE_REG'), - 'password_forgotten_enabled': get_global_setting('LOGIN_ENABLE_PWD_FORGOT'), - } - return Response(data) - - -class EmailAddressSerializer(InvenTreeModelSerializer): - """Serializer for the EmailAddress model.""" - - class Meta: - """Meta options for EmailAddressSerializer.""" - - model = EmailAddress - fields = '__all__' - - -class EmptyEmailAddressSerializer(InvenTreeModelSerializer): - """Empty Serializer for the EmailAddress model.""" - - class Meta: - """Meta options for EmailAddressSerializer.""" - - model = EmailAddress - fields = [] - - -class EmailListView(ListCreateAPI): - """List of registered email addresses for current users.""" - - permission_classes = (IsAuthenticated,) - serializer_class = EmailAddressSerializer - - def get_queryset(self): - """Only return data for current user.""" - return EmailAddress.objects.filter(user=self.request.user) - - -class EmailActionMixin(CreateAPI): - """Mixin to modify email addresses for current users.""" - - serializer_class = EmptyEmailAddressSerializer - permission_classes = (IsAuthenticated,) - - def get_queryset(self): - """Filter queryset for current user.""" - return EmailAddress.objects.filter( - user=self.request.user, pk=self.kwargs['pk'] - ).first() - - @extend_schema(responses={200: OpenApiResponse(response=EmailAddressSerializer)}) - def post(self, request, *args, **kwargs): - """Filter item, run action and return data.""" - email = self.get_queryset() - if not email: - raise NotFound - - self.special_action(email, request, *args, **kwargs) - return Response(EmailAddressSerializer(email).data) - - -class EmailVerifyView(EmailActionMixin): - """Re-verify an email for a currently logged in user.""" - - def special_action(self, email, request, *args, **kwargs): - """Send confirmation.""" - if email.verified: - return - email.send_confirmation(request) - - -class EmailPrimaryView(EmailActionMixin): - """Make an email for a currently logged in user primary.""" - - def special_action(self, email, *args, **kwargs): - """Mark email as primary.""" - if email.primary: - return - email.set_as_primary() - - -class EmailRemoveView(EmailActionMixin): - """Remove an email for a currently logged in user.""" - - def special_action(self, email, *args, **kwargs): - """Delete email.""" - email.delete() diff --git a/src/backend/InvenTree/InvenTree/sso.py b/src/backend/InvenTree/InvenTree/sso.py index b3fb551cf2f1..86d5c3346d7f 100644 --- a/src/backend/InvenTree/InvenTree/sso.py +++ b/src/backend/InvenTree/InvenTree/sso.py @@ -53,15 +53,6 @@ def check_provider(provider): return True -def provider_display_name(provider): - """Return the 'display name' for the given provider.""" - if app := get_provider_app(provider): - return app.name - - # Fallback value if app not found - return provider.name - - def login_enabled() -> bool: """Return True if SSO login is enabled.""" return str2bool(get_global_setting('LOGIN_ENABLE_SSO')) diff --git a/src/backend/InvenTree/InvenTree/urls.py b/src/backend/InvenTree/InvenTree/urls.py index a9fa1cd0e0f9..a0a3ce58c330 100644 --- a/src/backend/InvenTree/InvenTree/urls.py +++ b/src/backend/InvenTree/InvenTree/urls.py @@ -10,11 +10,6 @@ from django.views.decorators.csrf import csrf_exempt from django.views.generic.base import RedirectView -from dj_rest_auth.registration.views import ( - ConfirmEmailView, - SocialAccountDisconnectView, - SocialAccountListView, -) from drf_spectacular.views import SpectacularAPIView, SpectacularRedocView from sesame.views import LoginView @@ -47,14 +42,6 @@ VersionView, ) from .magic_login import GetSimpleLoginView -from .social_auth_urls import ( - EmailListView, - EmailPrimaryView, - EmailRemoveView, - EmailVerifyView, - SocialProviderListView, - social_auth_urlpatterns, -) from .views import ( AboutView, AppearanceSelectView, @@ -130,58 +117,12 @@ path( 'auth/', include([ - re_path( - r'^registration/account-confirm-email/(?P[-:\w]+)/$', - ConfirmEmailView.as_view(), - name='account_confirm_email', - ), - path('registration/', include('dj_rest_auth.registration.urls')), - path( - 'providers/', SocialProviderListView.as_view(), name='social_providers' - ), - path( - 'emails/', - include([ - path( - '/', - include([ - path( - 'primary/', - EmailPrimaryView.as_view(), - name='email-primary', - ), - path( - 'verify/', - EmailVerifyView.as_view(), - name='email-verify', - ), - path( - 'remove/', - EmailRemoveView().as_view(), - name='email-remove', - ), - ]), - ), - path('', EmailListView.as_view(), name='email-list'), - ]), - ), - path('social/', include(social_auth_urlpatterns)), - path( - 'social/', SocialAccountListView.as_view(), name='social_account_list' - ), - path( - 'social//disconnect/', - SocialAccountDisconnectView.as_view(), - name='social_account_disconnect', - ), - path('login/', users.api.Login.as_view(), name='api-login'), path('logout/', users.api.Logout.as_view(), name='api-logout'), path( 'login-redirect/', users.api.LoginRedirect.as_view(), name='api-login-redirect', ), - path('', include('dj_rest_auth.urls')), ]), ), # Magic login URLs @@ -410,7 +351,6 @@ path('stock/', include(stock_urls)), path('supplier-part/', include(supplier_part_urls)), path('edit-user/', EditUserView.as_view(), name='edit-user'), - path('set-password/', SetPasswordView.as_view(), name='set-password'), path('index/', IndexView.as_view(), name='index'), path('notifications/', include(notifications_urls)), path('search/', SearchView.as_view(), name='search'), diff --git a/src/backend/InvenTree/InvenTree/views.py b/src/backend/InvenTree/InvenTree/views.py index 176e704b19db..931520a40ff0 100644 --- a/src/backend/InvenTree/InvenTree/views.py +++ b/src/backend/InvenTree/InvenTree/views.py @@ -405,58 +405,6 @@ def get_object(self): return self.request.user -class SetPasswordView(AjaxUpdateView): - """View for setting user password.""" - - ajax_template_name = 'InvenTree/password.html' - ajax_form_title = _('Set Password') - form_class = SetPasswordForm - - def get_object(self): - """Set form to edit current user.""" - return self.request.user - - def post(self, request, *args, **kwargs): - """Validate inputs and change password.""" - form = self.get_form() - - valid = form.is_valid() - - p1 = request.POST.get('enter_password', '') - p2 = request.POST.get('confirm_password', '') - old_password = request.POST.get('old_password', '') - user = self.request.user - - if valid: - # Passwords must match - - if p1 != p2: - error = _('Password fields must match') - form.add_error('enter_password', error) - form.add_error('confirm_password', error) - valid = False - - if valid: - # Old password must be correct - if user.has_usable_password() and not user.check_password(old_password): - form.add_error('old_password', _('Wrong password provided')) - valid = False - - if valid: - try: - # Validate password - password_validation.validate_password(p1, user) - - # Update the user - user.set_password(p1) - user.save() - except ValidationError as error: - form.add_error('confirm_password', str(error)) - valid = False - - return self.renderJsonResponse(request, form, data={'form_valid': valid}) - - class IndexView(TemplateView): """View for InvenTree index page.""" diff --git a/src/backend/InvenTree/users/api.py b/src/backend/InvenTree/users/api.py index bbdc3be97550..c0b8e112201b 100644 --- a/src/backend/InvenTree/users/api.py +++ b/src/backend/InvenTree/users/api.py @@ -3,26 +3,17 @@ import datetime import logging -from django.contrib.auth import authenticate, get_user, login, logout +from django.contrib.auth import get_user, login from django.contrib.auth.models import Group, User -from django.http.response import HttpResponse -from django.shortcuts import redirect -from django.urls import include, path, re_path, reverse +from django.urls import include, path, re_path from django.views.generic.base import RedirectView -from allauth.account import app_settings -from allauth.account.adapter import get_adapter -from allauth_2fa.utils import user_has_valid_totp_device -from dj_rest_auth.views import LoginView, LogoutView from drf_spectacular.utils import OpenApiResponse, extend_schema, extend_schema_view from rest_framework import exceptions, permissions -from rest_framework.authentication import BasicAuthentication -from rest_framework.decorators import authentication_classes from rest_framework.response import Response from rest_framework.views import APIView import InvenTree.helpers -from common.models import InvenTreeSetting from InvenTree.filters import SEARCH_ORDER_FILTER from InvenTree.mixins import ( ListAPI, @@ -184,67 +175,12 @@ class GroupList(ListCreateAPI): ordering_fields = ['name'] -@authentication_classes([BasicAuthentication]) -@extend_schema_view( - post=extend_schema( - responses={200: OpenApiResponse(description='User successfully logged in')} - ) -) -class Login(LoginView): - """API view for logging in via API.""" - - def post(self, request, *args, **kwargs): - """API view for logging in via API.""" - _data = request.data.copy() - _data.update(request.POST.copy()) - - if not _data.get('mfa', None): - return super().post(request, *args, **kwargs) - - # Check if login credentials valid - user = authenticate( - request, username=_data.get('username'), password=_data.get('password') - ) - if user is None: - return HttpResponse(status=401) - - # Check if user has mfa set up - if not user_has_valid_totp_device(user): - return super().post(request, *args, **kwargs) - - # Stage login and redirect to 2fa - request.session['allauth_2fa_user_id'] = str(user.id) - request.session['allauth_2fa_login'] = { - 'email_verification': app_settings.EMAIL_VERIFICATION, - 'signal_kwargs': None, - 'signup': False, - 'email': None, - 'redirect_url': reverse('platform'), - } - return redirect(reverse('two-factor-authenticate')) - - def process_login(self): - """Process the login request, ensure that MFA is enforced if required.""" - # Normal login process - ret = super().process_login() - user = self.request.user - adapter = get_adapter(self.request) - - # User requires 2FA or MFA is enforced globally - no logins via API - if adapter.has_2fa_enabled(user) or InvenTreeSetting.get_setting( - 'LOGIN_ENFORCE_MFA' - ): - logout(self.request) - raise exceptions.PermissionDenied('MFA required for this user') - return ret - - @extend_schema_view( post=extend_schema( responses={200: OpenApiResponse(description='User successfully logged out')} ) ) -class Logout(LogoutView): +class Logout(APIView): """API view for logging out via API.""" serializer_class = None diff --git a/src/backend/requirements.in b/src/backend/requirements.in index 4dd4af8b4fd0..4de3ab4a4cc2 100644 --- a/src/backend/requirements.in +++ b/src/backend/requirements.in @@ -33,7 +33,6 @@ django-weasyprint # django weasyprint integration djangorestframework # DRF framework djangorestframework-simplejwt[crypto] # JWT authentication django-xforwardedfor-middleware # IP forwarding metadata -dj-rest-auth # Authentication API endpoints dulwich # pure Python git integration drf-spectacular # DRF API documentation feedparser # RSS newsfeed parser diff --git a/src/backend/requirements.txt b/src/backend/requirements.txt index 1dca2b204d0e..81012cc7cb00 100644 --- a/src/backend/requirements.txt +++ b/src/backend/requirements.txt @@ -327,13 +327,10 @@ diff-match-patch==20230430 \ --hash=sha256:953019cdb9c9d2c9e47b5b12bcff3cf4746fc4598eb406076fa1fc27e6a1f15c \ --hash=sha256:dce43505fb7b1b317de7195579388df0746d90db07015ed47a85e5e44930ef93 # via django-import-export -dj-rest-auth==6.0.0 \ - --hash=sha256:760b45f3a07cd6182e6a20fe07d0c55230c5f950167df724d7914d0dd8c50133 django==4.2.12 \ --hash=sha256:6a6b4aff8a2db2dc7dcc5650cb2c7a7a0d1eb38e2aa2335fdf001e41801e9797 \ --hash=sha256:7640e86835d44ae118c2916a803d8081f40e214ee18a5a92a0202994ca60a4b4 # via - # dj-rest-auth # django-allauth # django-cors-headers # django-dbbackup @@ -455,7 +452,6 @@ djangorestframework==3.14.0 \ --hash=sha256:579a333e6256b09489cbe0a067e66abe55c6595d8926be6b99423786334350c8 \ --hash=sha256:eb63f58c9f218e1a7d064d17a70751f528ed4e1d35547fdade9aaf4cd103fd08 # via - # dj-rest-auth # djangorestframework-simplejwt # drf-spectacular djangorestframework-simplejwt[crypto]==5.3.1 \ From f50f47601e2d324ba410def2dd8c02e60e3ac4e3 Mon Sep 17 00:00:00 2001 From: Matthias Mair Date: Tue, 25 Jun 2024 19:36:28 +0200 Subject: [PATCH 021/156] fix import --- src/backend/InvenTree/InvenTree/urls.py | 1 - 1 file changed, 1 deletion(-) diff --git a/src/backend/InvenTree/InvenTree/urls.py b/src/backend/InvenTree/InvenTree/urls.py index a0a3ce58c330..dde72230d2e5 100644 --- a/src/backend/InvenTree/InvenTree/urls.py +++ b/src/backend/InvenTree/InvenTree/urls.py @@ -57,7 +57,6 @@ IndexView, NotificationsView, SearchView, - SetPasswordView, SettingsView, auth_request, ) From 197d4f07535b4574d07628fe34dfdf90674e61cc Mon Sep 17 00:00:00 2001 From: Matthias Mair Date: Tue, 25 Jun 2024 19:46:46 +0200 Subject: [PATCH 022/156] mrege migrations --- .../migrations/0011_auto_20240119_1659.py | 18 ------------------ .../0012_migrate_mfa_20240408_1659.py | 5 ++++- 2 files changed, 4 insertions(+), 19 deletions(-) delete mode 100644 src/backend/InvenTree/users/migrations/0011_auto_20240119_1659.py diff --git a/src/backend/InvenTree/users/migrations/0011_auto_20240119_1659.py b/src/backend/InvenTree/users/migrations/0011_auto_20240119_1659.py deleted file mode 100644 index df72a83dd4c5..000000000000 --- a/src/backend/InvenTree/users/migrations/0011_auto_20240119_1659.py +++ /dev/null @@ -1,18 +0,0 @@ -# Generated by Django 3.2.23 on 2024-01-19 16:59 - -from django.db import migrations - - -class Migration(migrations.Migration): - dependencies = [ - ('users', '0010_alter_apitoken_key'), - ('otp_static', '0002_throttling'), - ('otp_totp', '0002_auto_20190420_0723'), - ('mfa', '0002_authenticator_timestamps'), - ] - - operations = [ - migrations.RunPython( - migrations.RunPython.noop, reverse_code=migrations.RunPython.noop - ) - ] diff --git a/src/backend/InvenTree/users/migrations/0012_migrate_mfa_20240408_1659.py b/src/backend/InvenTree/users/migrations/0012_migrate_mfa_20240408_1659.py index f126960ab77d..f38d5944095a 100644 --- a/src/backend/InvenTree/users/migrations/0012_migrate_mfa_20240408_1659.py +++ b/src/backend/InvenTree/users/migrations/0012_migrate_mfa_20240408_1659.py @@ -38,7 +38,10 @@ def move_mfa(apps, schema_editor): class Migration(migrations.Migration): - dependencies = [('users', '0011_auto_20240119_1659')] + dependencies = [('users', '0011_auto_20240523_1640'), + ('otp_static', '0002_throttling'), + ('otp_totp', '0002_auto_20190420_0723'), + ('mfa', '0002_authenticator_timestamps'),] operations = [ migrations.RunPython(move_mfa, reverse_code=migrations.RunPython.noop) From d16ca027aa3156c29eef924b14fc6d2d7d1e618f Mon Sep 17 00:00:00 2001 From: Matthias Mair Date: Tue, 25 Jun 2024 19:48:45 +0200 Subject: [PATCH 023/156] bump API version --- src/backend/InvenTree/InvenTree/api_version.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/backend/InvenTree/InvenTree/api_version.py b/src/backend/InvenTree/InvenTree/api_version.py index e385a2e4e601..e041556fa3d6 100644 --- a/src/backend/InvenTree/InvenTree/api_version.py +++ b/src/backend/InvenTree/InvenTree/api_version.py @@ -1,11 +1,15 @@ """InvenTree API version information.""" # InvenTree API version -INVENTREE_API_VERSION = 208 +INVENTREE_API_VERSION = 209 """Increment this API version number whenever there is a significant change to the API that any clients need to know about.""" INVENTREE_API_TEXT = """ +v209 - 2024-06-25 : https://github.com/inventree/InvenTree/pull/6293 + - Removes a considerable amount of old auth endpoints + - Introduces allauth based REST API + v208 - 2024-06-19 : https://github.com/inventree/InvenTree/pull/7479 - Adds documentation for the user roles API endpoint (no functional changes) From 83786009470a567f8158da43ca5011d3ca71dff5 Mon Sep 17 00:00:00 2001 From: Matthias Mair Date: Tue, 25 Jun 2024 20:06:56 +0200 Subject: [PATCH 024/156] switch to allauth.usersessions --- src/backend/InvenTree/InvenTree/settings.py | 5 +++-- src/backend/requirements.in | 2 +- src/backend/requirements.txt | 12 +++--------- 3 files changed, 7 insertions(+), 12 deletions(-) diff --git a/src/backend/InvenTree/InvenTree/settings.py b/src/backend/InvenTree/InvenTree/settings.py index 6fe0448e3154..09afa6f0a62e 100644 --- a/src/backend/InvenTree/InvenTree/settings.py +++ b/src/backend/InvenTree/InvenTree/settings.py @@ -204,7 +204,7 @@ # Core django modules 'django.contrib.auth', 'django.contrib.contenttypes', - 'user_sessions', # db user sessions + 'django.contrib.humanize', 'whitenoise.runserver_nostatic', 'django.contrib.messages', 'django.contrib.staticfiles', @@ -232,6 +232,7 @@ 'allauth.account', # Extend user with accounts 'allauth.socialaccount', # Use 'social' providers 'allauth.mfa', # MFA for for allauth + 'allauth.usersessions', # DB sessions 'django_otp', # OTP is needed for MFA - base package 'django_otp.plugins.otp_totp', # Time based OTP 'django_otp.plugins.otp_static', # Backup codes @@ -244,7 +245,7 @@ [ 'django.middleware.security.SecurityMiddleware', 'x_forwarded_for.middleware.XForwardedForMiddleware', - 'user_sessions.middleware.SessionMiddleware', # db user sessions + 'allauth.usersessions.middleware.UserSessionsMiddleware', # DB user sessions 'django.middleware.locale.LocaleMiddleware', 'django.middleware.csrf.CsrfViewMiddleware', 'corsheaders.middleware.CorsMiddleware', diff --git a/src/backend/requirements.in b/src/backend/requirements.in index 4de3ab4a4cc2..e544ce5a89a7 100644 --- a/src/backend/requirements.in +++ b/src/backend/requirements.in @@ -2,7 +2,7 @@ Django<5.0 # Django package coreapi # API documentation for djangorestframework cryptography>=40.0.0,!=40.0.2 # Core cryptographic functionality -django-allauth[mfa] # SSO for external providers via OpenID +django-allauth[mfa,socialaccount] # SSO for external providers via OpenID django-cleanup # Automated deletion of old / unused uploaded files django-cors-headers # CORS headers extension for DRF django-crispy-forms<2.0 # Form helpers # FIXED 2023-02-18 due to required updates in the new version diff --git a/src/backend/requirements.txt b/src/backend/requirements.txt index ea65ebaa3b55..dd12e6f05a5e 100644 --- a/src/backend/requirements.txt +++ b/src/backend/requirements.txt @@ -313,9 +313,7 @@ cssselect2==0.7.0 \ defusedxml==0.7.1 \ --hash=sha256:1bb3032db185915b62d7c6209c5a8792be6a32ab2fedacc84e01b52c51aa3e69 \ --hash=sha256:a352e7e428770286cc899e2542b6cdaedb2b4953ff269a210103ec58f6198a61 - # via - # odfpy - # python3-openid + # via odfpy deprecated==1.2.14 \ --hash=sha256:6fac8b097794a90302bdbb17b9b815e732d3c4720583ff1b198499d78470466c \ --hash=sha256:e5323eb936458dccc2582dc6f9c322c852a775a27065ff2b0c4970b9d53d01b3 @@ -359,8 +357,8 @@ django==4.2.12 \ # djangorestframework # djangorestframework-simplejwt # drf-spectacular -django-allauth[mfa]==0.61.1 \ - --hash=sha256:5b4ae515ea74f54f0041210692eee10c309ad15ddbbd03d3620693c75e3f7945 +django-allauth[mfa, socialaccount]==0.63.3 \ + --hash=sha256:2374164c468a309e6badf70bc3405136df6036f24a20a13387f2a063066bdaa9 django-cleanup==8.1.0 \ --hash=sha256:70df905076a44e7a111b31198199af633dee08876e199e6dce36ca8dd6b8b10f \ --hash=sha256:7903873ea73b3f7e61e055340d27dba49b70634f60c87a573ad748e172836458 @@ -964,10 +962,6 @@ python-fsutil==0.14.1 \ --hash=sha256:0d45e623f0f4403f674bdd8ae7aa7d24a4b3132ea45c65416bd2865e6b20b035 \ --hash=sha256:8fb204fa8059f37bdeee8a1dc0fff010170202ea47c4225ee71bb3c26f3997be # via django-maintenance-mode -python3-openid==3.2.0 \ - --hash=sha256:33fbf6928f401e0b790151ed2b5290b02545e8775f982485205a066f874aaeaf \ - --hash=sha256:6626f771e0417486701e0b4daff762e7212e820ca5b29fcc0d05f6f8736dfa6b - # via django-allauth pytz==2024.1 \ --hash=sha256:2a29735ea9c18baf14b448846bde5a48030ed267578472d8955cd0e7443a9812 \ --hash=sha256:328171f4e3623139da4983451950b28e95ac706e13f3f2630a879749e7a8b319 From 580057b76336c7ea145445f132fec23a14c96547 Mon Sep 17 00:00:00 2001 From: Matthias Mair Date: Tue, 25 Jun 2024 20:07:46 +0200 Subject: [PATCH 025/156] add headless --- src/backend/InvenTree/InvenTree/settings.py | 7 +++++++ src/backend/InvenTree/InvenTree/urls.py | 1 + 2 files changed, 8 insertions(+) diff --git a/src/backend/InvenTree/InvenTree/settings.py b/src/backend/InvenTree/InvenTree/settings.py index 09afa6f0a62e..743d4b896124 100644 --- a/src/backend/InvenTree/InvenTree/settings.py +++ b/src/backend/InvenTree/InvenTree/settings.py @@ -230,6 +230,7 @@ 'flags', # Flagging - django-flags 'allauth', # Base app for SSO 'allauth.account', # Extend user with accounts + 'allauth.headless', # APIs for auth 'allauth.socialaccount', # Use 'social' providers 'allauth.mfa', # MFA for for allauth 'allauth.usersessions', # DB sessions @@ -1182,6 +1183,12 @@ SOCIALACCOUNT_ADAPTER = 'InvenTree.forms.CustomSocialAccountAdapter' ACCOUNT_ADAPTER = 'InvenTree.forms.CustomAccountAdapter' +HEADLESS_FRONTEND_URLS = { + 'account_confirm_email': 'https://app.project.org/account/verify-email/{key}', + 'account_reset_password_from_key': 'https://app.org/account/password/reset/key/{key}', + 'account_signup': 'https://app.org/account/signup', +} + # Markdownify configuration # Ref: https://django-markdownify.readthedocs.io/en/latest/settings.html diff --git a/src/backend/InvenTree/InvenTree/urls.py b/src/backend/InvenTree/InvenTree/urls.py index dde72230d2e5..10c0790178cd 100644 --- a/src/backend/InvenTree/InvenTree/urls.py +++ b/src/backend/InvenTree/InvenTree/urls.py @@ -124,6 +124,7 @@ ), ]), ), + path('_allauth/', include('allauth.headless.urls')), # Magic login URLs path( 'email/generate/', From 7fe8062db86c8c323b33202ebf0d01f74bc4eef8 Mon Sep 17 00:00:00 2001 From: Matthias Mair Date: Tue, 25 Jun 2024 20:09:10 +0200 Subject: [PATCH 026/156] re-add saml/openid --- src/backend/requirements.in | 2 +- src/backend/requirements.txt | 226 ++++++++++++++++++++++++++++++++++- 2 files changed, 225 insertions(+), 3 deletions(-) diff --git a/src/backend/requirements.in b/src/backend/requirements.in index e544ce5a89a7..7109e2c77335 100644 --- a/src/backend/requirements.in +++ b/src/backend/requirements.in @@ -2,7 +2,7 @@ Django<5.0 # Django package coreapi # API documentation for djangorestframework cryptography>=40.0.0,!=40.0.2 # Core cryptographic functionality -django-allauth[mfa,socialaccount] # SSO for external providers via OpenID +django-allauth[mfa,socialaccount,saml,openid] # SSO for external providers via OpenID django-cleanup # Automated deletion of old / unused uploaded files django-cors-headers # CORS headers extension for DRF django-crispy-forms<2.0 # Form helpers # FIXED 2023-02-18 due to required updates in the new version diff --git a/src/backend/requirements.txt b/src/backend/requirements.txt index dd12e6f05a5e..b4e32e7ae296 100644 --- a/src/backend/requirements.txt +++ b/src/backend/requirements.txt @@ -313,7 +313,9 @@ cssselect2==0.7.0 \ defusedxml==0.7.1 \ --hash=sha256:1bb3032db185915b62d7c6209c5a8792be6a32ab2fedacc84e01b52c51aa3e69 \ --hash=sha256:a352e7e428770286cc899e2542b6cdaedb2b4953ff269a210103ec58f6198a61 - # via odfpy + # via + # odfpy + # python3-openid deprecated==1.2.14 \ --hash=sha256:6fac8b097794a90302bdbb17b9b815e732d3c4720583ff1b198499d78470466c \ --hash=sha256:e5323eb936458dccc2582dc6f9c322c852a775a27065ff2b0c4970b9d53d01b3 @@ -357,7 +359,7 @@ django==4.2.12 \ # djangorestframework # djangorestframework-simplejwt # drf-spectacular -django-allauth[mfa, socialaccount]==0.63.3 \ +django-allauth[mfa, openid, saml, socialaccount]==0.63.3 \ --hash=sha256:2374164c468a309e6badf70bc3405136df6036f24a20a13387f2a063066bdaa9 django-cleanup==8.1.0 \ --hash=sha256:70df905076a44e7a111b31198199af633dee08876e199e6dce36ca8dd6b8b10f \ @@ -637,6 +639,10 @@ inflection==0.5.1 \ --hash=sha256:1a29730d366e996aaacffb2f1f1cb9593dc38e2ddd30c91250c6dde09ea9b417 \ --hash=sha256:f38b2b640938a4f35ade69ac3d053042959b62a0f1076a5bbaa1b9526605a8a2 # via drf-spectacular +isodate==0.6.1 \ + --hash=sha256:0751eece944162659049d35f4f549ed815792b38793f07cf73381c1c87cbed96 \ + --hash=sha256:48c5881de7e8b0a0d648cb024c8062dc84e7b840ed81e864c7614fd3c127bde9 + # via python3-saml itypes==1.2.0 \ --hash=sha256:03da6872ca89d29aef62773672b2d408f490f80db48b23079a4b194c86dd04c6 \ --hash=sha256:af886f129dea4a2a1e3d36595a2d139589e4dd287f5cab0b40e799ee81570ff1 @@ -653,6 +659,152 @@ jsonschema-specifications==2023.12.1 \ --hash=sha256:48a76787b3e70f5ed53f1160d2b81f586e4ca6d1548c5de7085d1682674764cc \ --hash=sha256:87e4fdf3a94858b8a2ba2778d9ba57d8a9cafca7c7489c46ba0d30a8bc6a9c3c # via jsonschema +lxml==5.2.2 \ + --hash=sha256:02437fb7308386867c8b7b0e5bc4cd4b04548b1c5d089ffb8e7b31009b961dc3 \ + --hash=sha256:02f6a8eb6512fdc2fd4ca10a49c341c4e109aa6e9448cc4859af5b949622715a \ + --hash=sha256:05f8757b03208c3f50097761be2dea0aba02e94f0dc7023ed73a7bb14ff11eb0 \ + --hash=sha256:06668e39e1f3c065349c51ac27ae430719d7806c026fec462e5693b08b95696b \ + --hash=sha256:07542787f86112d46d07d4f3c4e7c760282011b354d012dc4141cc12a68cef5f \ + --hash=sha256:08ea0f606808354eb8f2dfaac095963cb25d9d28e27edcc375d7b30ab01abbf6 \ + --hash=sha256:0969e92af09c5687d769731e3f39ed62427cc72176cebb54b7a9d52cc4fa3b73 \ + --hash=sha256:0a028b61a2e357ace98b1615fc03f76eb517cc028993964fe08ad514b1e8892d \ + --hash=sha256:0b3f5016e00ae7630a4b83d0868fca1e3d494c78a75b1c7252606a3a1c5fc2ad \ + --hash=sha256:13e69be35391ce72712184f69000cda04fc89689429179bc4c0ae5f0b7a8c21b \ + --hash=sha256:16a8326e51fcdffc886294c1e70b11ddccec836516a343f9ed0f82aac043c24a \ + --hash=sha256:19b4e485cd07b7d83e3fe3b72132e7df70bfac22b14fe4bf7a23822c3a35bff5 \ + --hash=sha256:1a2569a1f15ae6c8c64108a2cd2b4a858fc1e13d25846be0666fc144715e32ab \ + --hash=sha256:1a7aca7964ac4bb07680d5c9d63b9d7028cace3e2d43175cb50bba8c5ad33316 \ + --hash=sha256:1b590b39ef90c6b22ec0be925b211298e810b4856909c8ca60d27ffbca6c12e6 \ + --hash=sha256:1d8a701774dfc42a2f0b8ccdfe7dbc140500d1049e0632a611985d943fcf12df \ + --hash=sha256:1e275ea572389e41e8b039ac076a46cb87ee6b8542df3fff26f5baab43713bca \ + --hash=sha256:2304d3c93f2258ccf2cf7a6ba8c761d76ef84948d87bf9664e14d203da2cd264 \ + --hash=sha256:23441e2b5339bc54dc949e9e675fa35efe858108404ef9aa92f0456929ef6fe8 \ + --hash=sha256:23cfafd56887eaed93d07bc4547abd5e09d837a002b791e9767765492a75883f \ + --hash=sha256:28bf95177400066596cdbcfc933312493799382879da504633d16cf60bba735b \ + --hash=sha256:2eb2227ce1ff998faf0cd7fe85bbf086aa41dfc5af3b1d80867ecfe75fb68df3 \ + --hash=sha256:2fb0ba3e8566548d6c8e7dd82a8229ff47bd8fb8c2da237607ac8e5a1b8312e5 \ + --hash=sha256:303f540ad2dddd35b92415b74b900c749ec2010e703ab3bfd6660979d01fd4ed \ + --hash=sha256:339ee4a4704bc724757cd5dd9dc8cf4d00980f5d3e6e06d5847c1b594ace68ab \ + --hash=sha256:33ce9e786753743159799fdf8e92a5da351158c4bfb6f2db0bf31e7892a1feb5 \ + --hash=sha256:343ab62e9ca78094f2306aefed67dcfad61c4683f87eee48ff2fd74902447726 \ + --hash=sha256:34e17913c431f5ae01d8658dbf792fdc457073dcdfbb31dc0cc6ab256e664a8d \ + --hash=sha256:364d03207f3e603922d0d3932ef363d55bbf48e3647395765f9bfcbdf6d23632 \ + --hash=sha256:38b67afb0a06b8575948641c1d6d68e41b83a3abeae2ca9eed2ac59892b36706 \ + --hash=sha256:3a745cc98d504d5bd2c19b10c79c61c7c3df9222629f1b6210c0368177589fb8 \ + --hash=sha256:3b019d4ee84b683342af793b56bb35034bd749e4cbdd3d33f7d1107790f8c472 \ + --hash=sha256:3b6a30a9ab040b3f545b697cb3adbf3696c05a3a68aad172e3fd7ca73ab3c835 \ + --hash=sha256:3d1e35572a56941b32c239774d7e9ad724074d37f90c7a7d499ab98761bd80cf \ + --hash=sha256:3d98de734abee23e61f6b8c2e08a88453ada7d6486dc7cdc82922a03968928db \ + --hash=sha256:453d037e09a5176d92ec0fd282e934ed26d806331a8b70ab431a81e2fbabf56d \ + --hash=sha256:45f9494613160d0405682f9eee781c7e6d1bf45f819654eb249f8f46a2c22545 \ + --hash=sha256:4820c02195d6dfb7b8508ff276752f6b2ff8b64ae5d13ebe02e7667e035000b9 \ + --hash=sha256:49095a38eb333aaf44c06052fd2ec3b8f23e19747ca7ec6f6c954ffea6dbf7be \ + --hash=sha256:4aefd911793b5d2d7a921233a54c90329bf3d4a6817dc465f12ffdfe4fc7b8fe \ + --hash=sha256:4bc6cb140a7a0ad1f7bc37e018d0ed690b7b6520ade518285dc3171f7a117905 \ + --hash=sha256:4c30a2f83677876465f44c018830f608fa3c6a8a466eb223535035fbc16f3438 \ + --hash=sha256:50127c186f191b8917ea2fb8b206fbebe87fd414a6084d15568c27d0a21d60db \ + --hash=sha256:50ccb5d355961c0f12f6cf24b7187dbabd5433f29e15147a67995474f27d1776 \ + --hash=sha256:519895c99c815a1a24a926d5b60627ce5ea48e9f639a5cd328bda0515ea0f10c \ + --hash=sha256:54401c77a63cc7d6dc4b4e173bb484f28a5607f3df71484709fe037c92d4f0ed \ + --hash=sha256:546cf886f6242dff9ec206331209db9c8e1643ae642dea5fdbecae2453cb50fd \ + --hash=sha256:55ce6b6d803890bd3cc89975fca9de1dff39729b43b73cb15ddd933b8bc20484 \ + --hash=sha256:56793b7a1a091a7c286b5f4aa1fe4ae5d1446fe742d00cdf2ffb1077865db10d \ + --hash=sha256:57f0a0bbc9868e10ebe874e9f129d2917750adf008fe7b9c1598c0fbbfdde6a6 \ + --hash=sha256:5b8c041b6265e08eac8a724b74b655404070b636a8dd6d7a13c3adc07882ef30 \ + --hash=sha256:5e097646944b66207023bc3c634827de858aebc226d5d4d6d16f0b77566ea182 \ + --hash=sha256:60499fe961b21264e17a471ec296dcbf4365fbea611bf9e303ab69db7159ce61 \ + --hash=sha256:610b5c77428a50269f38a534057444c249976433f40f53e3b47e68349cca1425 \ + --hash=sha256:625e3ef310e7fa3a761d48ca7ea1f9d8718a32b1542e727d584d82f4453d5eeb \ + --hash=sha256:657a972f46bbefdbba2d4f14413c0d079f9ae243bd68193cb5061b9732fa54c1 \ + --hash=sha256:69ab77a1373f1e7563e0fb5a29a8440367dec051da6c7405333699d07444f511 \ + --hash=sha256:6a520b4f9974b0a0a6ed73c2154de57cdfd0c8800f4f15ab2b73238ffed0b36e \ + --hash=sha256:6d68ce8e7b2075390e8ac1e1d3a99e8b6372c694bbe612632606d1d546794207 \ + --hash=sha256:6dcc3d17eac1df7859ae01202e9bb11ffa8c98949dcbeb1069c8b9a75917e01b \ + --hash=sha256:6dfdc2bfe69e9adf0df4915949c22a25b39d175d599bf98e7ddf620a13678585 \ + --hash=sha256:739e36ef7412b2bd940f75b278749106e6d025e40027c0b94a17ef7968d55d56 \ + --hash=sha256:7429e7faa1a60cad26ae4227f4dd0459efde239e494c7312624ce228e04f6391 \ + --hash=sha256:74da9f97daec6928567b48c90ea2c82a106b2d500f397eeb8941e47d30b1ca85 \ + --hash=sha256:74e4f025ef3db1c6da4460dd27c118d8cd136d0391da4e387a15e48e5c975147 \ + --hash=sha256:75a9632f1d4f698b2e6e2e1ada40e71f369b15d69baddb8968dcc8e683839b18 \ + --hash=sha256:76acba4c66c47d27c8365e7c10b3d8016a7da83d3191d053a58382311a8bf4e1 \ + --hash=sha256:79d1fb9252e7e2cfe4de6e9a6610c7cbb99b9708e2c3e29057f487de5a9eaefa \ + --hash=sha256:7ce7ad8abebe737ad6143d9d3bf94b88b93365ea30a5b81f6877ec9c0dee0a48 \ + --hash=sha256:7ed07b3062b055d7a7f9d6557a251cc655eed0b3152b76de619516621c56f5d3 \ + --hash=sha256:7ff762670cada8e05b32bf1e4dc50b140790909caa8303cfddc4d702b71ea184 \ + --hash=sha256:8268cbcd48c5375f46e000adb1390572c98879eb4f77910c6053d25cc3ac2c67 \ + --hash=sha256:875a3f90d7eb5c5d77e529080d95140eacb3c6d13ad5b616ee8095447b1d22e7 \ + --hash=sha256:89feb82ca055af0fe797a2323ec9043b26bc371365847dbe83c7fd2e2f181c34 \ + --hash=sha256:8a7e24cb69ee5f32e003f50e016d5fde438010c1022c96738b04fc2423e61706 \ + --hash=sha256:8ab6a358d1286498d80fe67bd3d69fcbc7d1359b45b41e74c4a26964ca99c3f8 \ + --hash=sha256:8b8df03a9e995b6211dafa63b32f9d405881518ff1ddd775db4e7b98fb545e1c \ + --hash=sha256:8cf85a6e40ff1f37fe0f25719aadf443686b1ac7652593dc53c7ef9b8492b115 \ + --hash=sha256:8e8d351ff44c1638cb6e980623d517abd9f580d2e53bfcd18d8941c052a5a009 \ + --hash=sha256:9164361769b6ca7769079f4d426a41df6164879f7f3568be9086e15baca61466 \ + --hash=sha256:96e85aa09274955bb6bd483eaf5b12abadade01010478154b0ec70284c1b1526 \ + --hash=sha256:981a06a3076997adf7c743dcd0d7a0415582661e2517c7d961493572e909aa1d \ + --hash=sha256:9cd5323344d8ebb9fb5e96da5de5ad4ebab993bbf51674259dbe9d7a18049525 \ + --hash=sha256:9d6c6ea6a11ca0ff9cd0390b885984ed31157c168565702959c25e2191674a14 \ + --hash=sha256:a02d3c48f9bb1e10c7788d92c0c7db6f2002d024ab6e74d6f45ae33e3d0288a3 \ + --hash=sha256:a233bb68625a85126ac9f1fc66d24337d6e8a0f9207b688eec2e7c880f012ec0 \ + --hash=sha256:a2f6a1bc2460e643785a2cde17293bd7a8f990884b822f7bca47bee0a82fc66b \ + --hash=sha256:a6d17e0370d2516d5bb9062c7b4cb731cff921fc875644c3d751ad857ba9c5b1 \ + --hash=sha256:a6d2092797b388342c1bc932077ad232f914351932353e2e8706851c870bca1f \ + --hash=sha256:ab67ed772c584b7ef2379797bf14b82df9aa5f7438c5b9a09624dd834c1c1aaf \ + --hash=sha256:ac6540c9fff6e3813d29d0403ee7a81897f1d8ecc09a8ff84d2eea70ede1cdbf \ + --hash=sha256:ae4073a60ab98529ab8a72ebf429f2a8cc612619a8c04e08bed27450d52103c0 \ + --hash=sha256:ae791f6bd43305aade8c0e22f816b34f3b72b6c820477aab4d18473a37e8090b \ + --hash=sha256:aef5474d913d3b05e613906ba4090433c515e13ea49c837aca18bde190853dff \ + --hash=sha256:b0b3f2df149efb242cee2ffdeb6674b7f30d23c9a7af26595099afaf46ef4e88 \ + --hash=sha256:b128092c927eaf485928cec0c28f6b8bead277e28acf56800e972aa2c2abd7a2 \ + --hash=sha256:b16db2770517b8799c79aa80f4053cd6f8b716f21f8aca962725a9565ce3ee40 \ + --hash=sha256:b336b0416828022bfd5a2e3083e7f5ba54b96242159f83c7e3eebaec752f1716 \ + --hash=sha256:b47633251727c8fe279f34025844b3b3a3e40cd1b198356d003aa146258d13a2 \ + --hash=sha256:b537bd04d7ccd7c6350cdaaaad911f6312cbd61e6e6045542f781c7f8b2e99d2 \ + --hash=sha256:b5e4ef22ff25bfd4ede5f8fb30f7b24446345f3e79d9b7455aef2836437bc38a \ + --hash=sha256:b74b9ea10063efb77a965a8d5f4182806fbf59ed068b3c3fd6f30d2ac7bee734 \ + --hash=sha256:bb2dc4898180bea79863d5487e5f9c7c34297414bad54bcd0f0852aee9cfdb87 \ + --hash=sha256:bbc4b80af581e18568ff07f6395c02114d05f4865c2812a1f02f2eaecf0bfd48 \ + --hash=sha256:bcc98f911f10278d1daf14b87d65325851a1d29153caaf146877ec37031d5f36 \ + --hash=sha256:be49ad33819d7dcc28a309b86d4ed98e1a65f3075c6acd3cd4fe32103235222b \ + --hash=sha256:bec4bd9133420c5c52d562469c754f27c5c9e36ee06abc169612c959bd7dbb07 \ + --hash=sha256:c2faf60c583af0d135e853c86ac2735ce178f0e338a3c7f9ae8f622fd2eb788c \ + --hash=sha256:c689d0d5381f56de7bd6966a4541bff6e08bf8d3871bbd89a0c6ab18aa699573 \ + --hash=sha256:c7079d5eb1c1315a858bbf180000757db8ad904a89476653232db835c3114001 \ + --hash=sha256:cb3942960f0beb9f46e2a71a3aca220d1ca32feb5a398656be934320804c0df9 \ + --hash=sha256:cd9e78285da6c9ba2d5c769628f43ef66d96ac3085e59b10ad4f3707980710d3 \ + --hash=sha256:cf2a978c795b54c539f47964ec05e35c05bd045db5ca1e8366988c7f2fe6b3ce \ + --hash=sha256:d14a0d029a4e176795cef99c056d58067c06195e0c7e2dbb293bf95c08f772a3 \ + --hash=sha256:d237ba6664b8e60fd90b8549a149a74fcc675272e0e95539a00522e4ca688b04 \ + --hash=sha256:d26a618ae1766279f2660aca0081b2220aca6bd1aa06b2cf73f07383faf48927 \ + --hash=sha256:d28cb356f119a437cc58a13f8135ab8a4c8ece18159eb9194b0d269ec4e28083 \ + --hash=sha256:d4ed0c7cbecde7194cd3228c044e86bf73e30a23505af852857c09c24e77ec5d \ + --hash=sha256:d83e2d94b69bf31ead2fa45f0acdef0757fa0458a129734f59f67f3d2eb7ef32 \ + --hash=sha256:d8bbcd21769594dbba9c37d3c819e2d5847656ca99c747ddb31ac1701d0c0ed9 \ + --hash=sha256:d9b342c76003c6b9336a80efcc766748a333573abf9350f4094ee46b006ec18f \ + --hash=sha256:dc911208b18842a3a57266d8e51fc3cfaccee90a5351b92079beed912a7914c2 \ + --hash=sha256:dfa7c241073d8f2b8e8dbc7803c434f57dbb83ae2a3d7892dd068d99e96efe2c \ + --hash=sha256:e282aedd63c639c07c3857097fc0e236f984ceb4089a8b284da1c526491e3f3d \ + --hash=sha256:e290d79a4107d7d794634ce3e985b9ae4f920380a813717adf61804904dc4393 \ + --hash=sha256:e3d9d13603410b72787579769469af730c38f2f25505573a5888a94b62b920f8 \ + --hash=sha256:e481bba1e11ba585fb06db666bfc23dbe181dbafc7b25776156120bf12e0d5a6 \ + --hash=sha256:e49b052b768bb74f58c7dda4e0bdf7b79d43a9204ca584ffe1fb48a6f3c84c66 \ + --hash=sha256:eb00b549b13bd6d884c863554566095bf6fa9c3cecb2e7b399c4bc7904cb33b5 \ + --hash=sha256:ec87c44f619380878bd49ca109669c9f221d9ae6883a5bcb3616785fa8f94c97 \ + --hash=sha256:edcfa83e03370032a489430215c1e7783128808fd3e2e0a3225deee278585196 \ + --hash=sha256:f11ae142f3a322d44513de1018b50f474f8f736bc3cd91d969f464b5bfef8836 \ + --hash=sha256:f2a09f6184f17a80897172863a655467da2b11151ec98ba8d7af89f17bf63dae \ + --hash=sha256:f5b65529bb2f21ac7861a0e94fdbf5dc0daab41497d18223b46ee8515e5ad297 \ + --hash=sha256:f60fdd125d85bf9c279ffb8e94c78c51b3b6a37711464e1f5f31078b45002421 \ + --hash=sha256:f61efaf4bed1cc0860e567d2ecb2363974d414f7f1f124b1df368bbf183453a6 \ + --hash=sha256:f90e552ecbad426eab352e7b2933091f2be77115bb16f09f78404861c8322981 \ + --hash=sha256:f956196ef61369f1685d14dad80611488d8dc1ef00be57c0c5a03064005b0f30 \ + --hash=sha256:fb91819461b1b56d06fa4bcf86617fac795f6a99d12239fb0c68dbeba41a0a30 \ + --hash=sha256:fbc9d316552f9ef7bba39f4edfad4a734d3d6f93341232a9dddadec4f15d425f \ + --hash=sha256:ff69a9a0b4b17d78170c73abe2ab12084bdf1691550c5629ad1fe7849433f324 \ + --hash=sha256:ffb2be176fed4457e445fe540617f0252a72a8bc56208fd65a690fdb1f57660b + # via + # python3-saml + # xmlsec markdown==3.6 \ --hash=sha256:48f276f4d8cfb8ce6527c8f79e2ee29708508bf4d40aa410fbc3b4ee832c850f \ --hash=sha256:ed4f41f6daecbeeb96e576ce414c41d2d876daa9a16cb35fa8ed8c2ddfad0224 @@ -962,6 +1114,15 @@ python-fsutil==0.14.1 \ --hash=sha256:0d45e623f0f4403f674bdd8ae7aa7d24a4b3132ea45c65416bd2865e6b20b035 \ --hash=sha256:8fb204fa8059f37bdeee8a1dc0fff010170202ea47c4225ee71bb3c26f3997be # via django-maintenance-mode +python3-openid==3.2.0 \ + --hash=sha256:33fbf6928f401e0b790151ed2b5290b02545e8775f982485205a066f874aaeaf \ + --hash=sha256:6626f771e0417486701e0b4daff762e7212e820ca5b29fcc0d05f6f8736dfa6b + # via django-allauth +python3-saml==1.16.0 \ + --hash=sha256:20b97d11b04f01ee22e98f4a38242e2fea2e28fbc7fbc9bdd57cab5ac7fc2d0d \ + --hash=sha256:97c9669aecabc283c6e5fb4eb264f446b6e006f5267d01c9734f9d8bffdac133 \ + --hash=sha256:c49097863c278ff669a337a96c46dc1f25d16307b4bb2679d2d1733cc4f5176a + # via django-allauth pytz==2024.1 \ --hash=sha256:2a29735ea9c18baf14b448846bde5a48030ed267578472d8955cd0e7443a9812 \ --hash=sha256:328171f4e3623139da4983451950b28e95ac706e13f3f2630a879749e7a8b319 @@ -1343,6 +1504,7 @@ six==1.16.0 \ # via # bleach # html5lib + # isodate # python-dateutil sqlparse==0.5.0 \ --hash=sha256:714d0a4932c059d16189f58ef5411ec2287a4360f17cdd0edd2d09d4c5087c93 \ @@ -1485,6 +1647,66 @@ xlwt==1.3.0 \ --hash=sha256:a082260524678ba48a297d922cc385f58278b8aa68741596a87de01a9c628b2e \ --hash=sha256:c59912717a9b28f1a3c2a98fd60741014b06b043936dcecbc113eaaada156c88 # via tablib +xmlsec==1.3.14 \ + --hash=sha256:004e8a82e26728bf8a60f8ece1ef3ffafdac30ef538139dfe28870e8503ca64a \ + --hash=sha256:03ccba7dacf197850de954666af0221c740a5de631a80136362a1559223fab75 \ + --hash=sha256:0bae37b2920115cf00759ee9fb7841cbdebcef3a8a92734ab93ae8fa41ac581d \ + --hash=sha256:0be3b7a28e54a03b87faf07fb3c6dc3e50a2c79b686718c3ad08300b8bf6bb67 \ + --hash=sha256:1072878301cb9243a54679e0520e6a5be2266c07a28b0ecef9e029d05a90ffcd \ + --hash=sha256:12d90059308bb0c1b94bde065784e6852999d08b91bcb2048c17e62b954acb07 \ + --hash=sha256:147934bd39dfd840663fb6b920ea9201455fa886427975713f1b42d9f20b9b29 \ + --hash=sha256:19c86bab1498e4c2e56d8e2c878f461ccb6e56b67fd7522b0c8fda46d8910781 \ + --hash=sha256:1b9b5de6bc69fdec23147e5f712cb05dc86df105462f254f140d743cc680cc7b \ + --hash=sha256:1eb3dcf244a52f796377112d8f238dbb522eb87facffb498425dc8582a84a6bf \ + --hash=sha256:1fa1311f7489d050dde9028f5a2b5849c2927bb09c9a93491cb2f28fdc563912 \ + --hash=sha256:1fe23c2dd5f5dbcb24f40e2c1061e2672a32aabee7cf8ac5337036a485607d72 \ + --hash=sha256:204d3c586b8bd6f02a5d4c59850a8157205569d40c32567f49576fa5795d897d \ + --hash=sha256:2401e162aaab7d9416c3405bac7a270e5f370988a0f1f46f0f29b735edba87e1 \ + --hash=sha256:28cd9f513cf01dc0c5b9d9f0728714ecde2e7f46b3b6f63de91f4ae32f3008b3 \ + --hash=sha256:2f84a1c509c52773365645a87949081ee9ea9c535cd452048cc8ca4ad3b45666 \ + --hash=sha256:330147ce59fbe56a9be5b2085d739c55a569f112576b3f1b33681f87416eaf33 \ + --hash=sha256:34c61ec0c0e70fda710290ae74b9efe1928d9242ed82c4eecf97aa696cff68e6 \ + --hash=sha256:38e035bf48300b7dbde2dd01d3b8569f8584fc9c73809be13886e6b6c77b74fb \ + --hash=sha256:48e894ad3e7de373f56efc09d6a56f7eae73a8dd4cec8943313134849e9c6607 \ + --hash=sha256:4922afa9234d1c5763950b26c328a5320019e55eb6000272a79dfe54fee8e704 \ + --hash=sha256:4af81ce8044862ec865782efd353d22abdcd95b92364eef3c934de57ae6d5852 \ + --hash=sha256:4dea6df3ffcb65d0b215678c3a0fe7bbc66785d6eae81291296e372498bad43a \ + --hash=sha256:4edd8db4df04bbac9c4a5ab4af855b74fe2bf2c248d07cac2e6d92a485f1a685 \ + --hash=sha256:4fac2a787ae3b9fb761f9aec6b9f10f2d1c1b87abb574ebd8ff68435bdc97e3d \ + --hash=sha256:57fed3bc7943681c9ed4d2221600ab440f060d8d1a8f92f346f2b41effe175b8 \ + --hash=sha256:6566434e2e5c58e472362a6187f208601f1627a148683a6f92bd16479f1d9e20 \ + --hash=sha256:6679cec780386d848e7351d4b0de92c4483289ea4f0a2187e216159f939a4c6b \ + --hash=sha256:73eabf5ef58189d81655058cf328c1dfa9893d89f1bff5fc941481f08533f338 \ + --hash=sha256:774d5d1e45f07f953c1cc14fd055c1063f0725f7248b6b0e681f59fd8638934d \ + --hash=sha256:77749b338503fb6e151052c664064b34264f4168e2cb0cca1de78b7e5312a783 \ + --hash=sha256:7799a9ff3593f9dd43464e18b1a621640bffc40456c47c23383727f937dca7fc \ + --hash=sha256:7882963e9cb9c0bd0e8c2715a29159a366417ff4a30d8baf42b05bc5cf249446 \ + --hash=sha256:7e8e0171916026cbe8e2022c959558d02086655fd3c3466f2bc0451b09cf9ee8 \ + --hash=sha256:82ac81deb7d7bf5cc8a748148948e5df5386597ff43fb92ec651cc5c7addb0e7 \ + --hash=sha256:86ff7b2711557c1087b72b0a1a88d82eafbf2a6d38b97309a6f7101d4a7041c3 \ + --hash=sha256:934f804f2f895bcdb86f1eaee236b661013560ee69ec108d29cdd6e5f292a2d9 \ + --hash=sha256:995e87acecc263a2f6f2aa3cc204268f651cac8f4d7a2047f11b2cd49979cc38 \ + --hash=sha256:a487c3d144f791c32f5e560aa27a705fba23171728b8a8511f36de053ff6bc93 \ + --hash=sha256:a98eadfcb0c3b23ccceb7a2f245811f8d784bd287640dcfe696a26b9db1e2fc0 \ + --hash=sha256:ad1634cabe0915fe2a12e142db0ed2daf5be80cbe3891a2cecbba0750195cc6b \ + --hash=sha256:b109cdf717257fd4daa77c1d3ec8a3fb2a81318a6d06a36c55a8a53ae381ae5e \ + --hash=sha256:b6dd86f440fec9242515c64f0be93fec8b4289287db1f6de2651eee9995aaecb \ + --hash=sha256:b7ba2ea38e3d9efa520b14f3c0b7d99a7c055244ae5ba8bc9f4ca73b18f3a215 \ + --hash=sha256:ba3b39c493e3b04354615068a3218f30897fcc2f42c6d8986d0c1d63aca87782 \ + --hash=sha256:bd10ca3201f164482775a7ce61bf7ee9aade2e7d032046044dd0f6f52c91d79d \ + --hash=sha256:bddd2a2328b4e08c8a112e06cf2cd2b4d281f4ad94df15b4cef18f06cdc49d78 \ + --hash=sha256:c12900e1903e289deb84eb893dca88591d6884d3e3cda4fb711b8812118416e8 \ + --hash=sha256:c42735cc68fdb4c6065cf0a0701dfff3a12a1734c63a36376349af9a5481f27b \ + --hash=sha256:c4d41c83c8a2b8d8030204391ebeb6174fbdb044f0331653c4b5a4ce4150bcc0 \ + --hash=sha256:ce4e165a1436697e5e39587c4fba24db4545a5c9801e0d749f1afd09ad3ab901 \ + --hash=sha256:cf35a25be3eb6263b2e0544ba26294651113fab79064f994d347a2ca5973e8e2 \ + --hash=sha256:d0762f4232bce2c7f6c0af329db8b821b4460bbe123a2528fb5677d03db7a4b5 \ + --hash=sha256:dba457ff87c39cbae3c5020475a728d24bbd9d00376df9af9724cd3bb59ff07a \ + --hash=sha256:df4aa0782a53032fd35e18dcd6d328d6126324bfcfdef0cb5c2856f25b4b6f94 \ + --hash=sha256:e6cbc914d77678db0c8bc39e723d994174633d18f9d6be4665ec29cce978a96d \ + --hash=sha256:e732a75fcb6b84872b168f972fbbf3749baf76308635f14015d1d35ed0c5719c \ + --hash=sha256:ed4034939d8566ccdcd3b4e4f23c63fd807fb8763ae5668d59a19e11640a8242 + # via python3-saml zipp==3.18.1 \ --hash=sha256:206f5a15f2af3dbaee80769fb7dc6f249695e940acca08dfb2a4769fe61e538b \ --hash=sha256:2884ed22e7d8961de1c9a05142eb69a247f120291bc0206a00a7642f09b5b715 From 8b1f871b750c71366ee051da85b42a0cd5b546a1 Mon Sep 17 00:00:00 2001 From: Matthias Mair Date: Tue, 25 Jun 2024 20:38:01 +0200 Subject: [PATCH 027/156] user sessions cleanup --- src/backend/InvenTree/InvenTree/settings.py | 10 +++----- src/backend/InvenTree/InvenTree/urls.py | 13 ---------- src/backend/InvenTree/InvenTree/views.py | 25 +------------------ .../templates/InvenTree/settings/user.html | 4 +-- src/backend/InvenTree/users/models.py | 1 - src/backend/requirements.in | 1 - src/backend/requirements.txt | 4 --- tasks.py | 1 - 8 files changed, 6 insertions(+), 53 deletions(-) diff --git a/src/backend/InvenTree/InvenTree/settings.py b/src/backend/InvenTree/InvenTree/settings.py index 743d4b896124..5e8909b8d963 100644 --- a/src/backend/InvenTree/InvenTree/settings.py +++ b/src/backend/InvenTree/InvenTree/settings.py @@ -204,6 +204,7 @@ # Core django modules 'django.contrib.auth', 'django.contrib.contenttypes', + 'django.contrib.sessions', 'django.contrib.humanize', 'whitenoise.runserver_nostatic', 'django.contrib.messages', @@ -246,6 +247,7 @@ [ 'django.middleware.security.SecurityMiddleware', 'x_forwarded_for.middleware.XForwardedForMiddleware', + 'django.contrib.sessions.middleware.SessionMiddleware', 'allauth.usersessions.middleware.UserSessionsMiddleware', # DB user sessions 'django.middleware.locale.LocaleMiddleware', 'django.middleware.csrf.CsrfViewMiddleware', @@ -812,13 +814,7 @@ # as well Q_CLUSTER['django_redis'] = 'worker' -# database user sessions -SESSION_ENGINE = 'user_sessions.backends.db' -LOGOUT_REDIRECT_URL = get_setting( - 'INVENTREE_LOGOUT_REDIRECT_URL', 'logout_redirect_url', 'index' -) - -SILENCED_SYSTEM_CHECKS = ['admin.E410', 'templates.E003', 'templates.W003'] +SILENCED_SYSTEM_CHECKS = ['templates.E003', 'templates.W003'] # Password validation # https://docs.djangoproject.com/en/1.10/ref/settings/#auth-password-validators diff --git a/src/backend/InvenTree/InvenTree/urls.py b/src/backend/InvenTree/InvenTree/urls.py index 10c0790178cd..accca0a7e11b 100644 --- a/src/backend/InvenTree/InvenTree/urls.py +++ b/src/backend/InvenTree/InvenTree/urls.py @@ -49,8 +49,6 @@ CustomEmailView, CustomLoginView, CustomPasswordResetFromKeyView, - CustomSessionDeleteOtherView, - CustomSessionDeleteView, DatabaseStatsView, DynamicJsView, EditUserView, @@ -357,17 +355,6 @@ path('settings/', include(settings_urls)), path('about/', AboutView.as_view(), name='about'), path('stats/', DatabaseStatsView.as_view(), name='stats'), - # DB user sessions - path( - 'accounts/sessions/other/delete/', - view=CustomSessionDeleteOtherView.as_view(), - name='session_delete_other', - ), - re_path( - r'^accounts/sessions/(?P\w+)/delete/$', - view=CustomSessionDeleteView.as_view(), - name='session_delete', - ), # Single Sign On / allauth # overrides of urlpatterns path('accounts/email/', CustomEmailView.as_view(), name='account_email'), diff --git a/src/backend/InvenTree/InvenTree/views.py b/src/backend/InvenTree/InvenTree/views.py index 931520a40ff0..557db269c12b 100644 --- a/src/backend/InvenTree/InvenTree/views.py +++ b/src/backend/InvenTree/InvenTree/views.py @@ -4,9 +4,7 @@ as JSON objects and passing them to modal forms (using jQuery / bootstrap). """ -from django.contrib.auth import password_validation from django.contrib.auth.mixins import LoginRequiredMixin, PermissionRequiredMixin -from django.core.exceptions import ValidationError from django.http import HttpResponse, HttpResponseRedirect, JsonResponse from django.shortcuts import redirect from django.template.loader import render_to_string @@ -23,14 +21,13 @@ from allauth.socialaccount.forms import DisconnectForm from allauth.socialaccount.views import ConnectionsView from djmoney.contrib.exchange.models import ExchangeBackend, Rate -from user_sessions.views import SessionDeleteOtherView, SessionDeleteView import common.currency import common.models as common_models from part.models import PartCategory from users.models import RuleSet, check_user_role -from .forms import EditUserForm, SetPasswordForm +from .forms import EditUserForm from .helpers import is_ajax, remove_non_printable_characters, strip_html_tags @@ -515,26 +512,6 @@ class CustomPasswordResetFromKeyView(PasswordResetFromKeyView): success_url = reverse_lazy('account_login') -class UserSessionOverride: - """Overrides sucessurl to lead to settings.""" - - def get_success_url(self): - """Revert to settings page after success.""" - return str(reverse_lazy('settings')) - - -class CustomSessionDeleteView(UserSessionOverride, SessionDeleteView): - """Revert to settings after session delete.""" - - pass - - -class CustomSessionDeleteOtherView(UserSessionOverride, SessionDeleteOtherView): - """Revert to settings after session delete.""" - - pass - - class CustomLoginView(LoginView): """Custom login view that allows login with urlargs.""" diff --git a/src/backend/InvenTree/templates/InvenTree/settings/user.html b/src/backend/InvenTree/templates/InvenTree/settings/user.html index 98ded18abeeb..eb3a451c4cc8 100644 --- a/src/backend/InvenTree/templates/InvenTree/settings/user.html +++ b/src/backend/InvenTree/templates/InvenTree/settings/user.html @@ -5,7 +5,7 @@ {% load inventree_extras %} {% load socialaccount %} {% load crispy_forms_tags %} -{% load user_sessions i18n %} +{% load i18n %} {% block label %}account{% endblock label %} @@ -194,7 +194,7 @@

{% trans "Active Sessions" %}

{{ object.ip }} {% if object.user_agent or object.device %} - {{ object.user_agent|device|default_if_none:unknown_on_unknown|safe }} + {{ object.user_agent|default_if_none:unknown_on_unknown|safe }} {% else %} {{ unknown_on_unknown }} {% endif %} diff --git a/src/backend/InvenTree/users/models.py b/src/backend/InvenTree/users/models.py index 3d801571524c..20330a2e845e 100644 --- a/src/backend/InvenTree/users/models.py +++ b/src/backend/InvenTree/users/models.py @@ -351,7 +351,6 @@ def get_ruleset_ignore(): 'error_report_error', 'exchange_rate', 'exchange_exchangebackend', - 'user_sessions_session', # Django-q 'django_q_ormq', 'django_q_failure', diff --git a/src/backend/requirements.in b/src/backend/requirements.in index 7109e2c77335..47f7a1fda49a 100644 --- a/src/backend/requirements.in +++ b/src/backend/requirements.in @@ -28,7 +28,6 @@ django-sslserver # Secure HTTP development server django-stdimage # Advanced ImageField management django-taggit # Tagging support django-otp==1.3.0 # Two-factor authentication (legacy to ensure migrations) https://github.com/inventree/InvenTree/pull/6293 -django-user-sessions # user sessions in DB django-weasyprint # django weasyprint integration djangorestframework # DRF framework djangorestframework-simplejwt[crypto] # JWT authentication diff --git a/src/backend/requirements.txt b/src/backend/requirements.txt index b4e32e7ae296..ee010c25254c 100644 --- a/src/backend/requirements.txt +++ b/src/backend/requirements.txt @@ -353,7 +353,6 @@ django==4.2.12 \ # django-sslserver # django-stdimage # django-taggit - # django-user-sessions # django-weasyprint # django-xforwardedfor-middleware # djangorestframework @@ -440,9 +439,6 @@ django-stdimage==6.0.2 \ django-taggit==5.0.1 \ --hash=sha256:a0ca8a28b03c4b26c2630fd762cb76ec39b5e41abf727a7b66f897a625c5e647 \ --hash=sha256:edcd7db1e0f35c304e082a2f631ddac2e16ef5296029524eb792af7430cab4cc -django-user-sessions==2.0.0 \ - --hash=sha256:0965554279f556b47062965609fa08b3ae45bbc581001dbe84b2ea599cc67748 \ - --hash=sha256:41b8b1ebeb4736065efbc96437c9cfbf491c39e10fd547a76b98f2312e11fa3e django-weasyprint==2.3.0 \ --hash=sha256:2f849e15bfd6c1b2a58512097b9042eddf3533651d37d2e096cd6f7d8be6442b \ --hash=sha256:807cb3b16332123d97c8bbe2ac9c70286103fe353235351803ffd33b67284735 diff --git a/tasks.py b/tasks.py index df7e456d2f9b..19a951fe07d3 100644 --- a/tasks.py +++ b/tasks.py @@ -85,7 +85,6 @@ def content_excludes( 'exchange.exchangebackend', 'common.notificationentry', 'common.notificationmessage', - 'user_sessions.session', 'report.labeloutput', 'report.reportoutput', ] From daacfaf580a5e2add24e34f37deff75bbd179813 Mon Sep 17 00:00:00 2001 From: Matthias Mair Date: Tue, 25 Jun 2024 20:38:38 +0200 Subject: [PATCH 028/156] turn off normal allauth urls if CUI is not active --- src/backend/InvenTree/InvenTree/settings.py | 1 + src/backend/InvenTree/InvenTree/urls.py | 7 ++++++- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/src/backend/InvenTree/InvenTree/settings.py b/src/backend/InvenTree/InvenTree/settings.py index 5e8909b8d963..13e43752792b 100644 --- a/src/backend/InvenTree/InvenTree/settings.py +++ b/src/backend/InvenTree/InvenTree/settings.py @@ -1184,6 +1184,7 @@ 'account_reset_password_from_key': 'https://app.org/account/password/reset/key/{key}', 'account_signup': 'https://app.org/account/signup', } +HEADLESS_ONLY = not ENABLE_CLASSIC_FRONTEND # Markdownify configuration # Ref: https://django-markdownify.readthedocs.io/en/latest/settings.html diff --git a/src/backend/InvenTree/InvenTree/urls.py b/src/backend/InvenTree/InvenTree/urls.py index accca0a7e11b..99b9814d9919 100644 --- a/src/backend/InvenTree/InvenTree/urls.py +++ b/src/backend/InvenTree/InvenTree/urls.py @@ -370,7 +370,6 @@ ), # Override login page path('accounts/login/', CustomLoginView.as_view(), name='account_login'), - path('accounts/', include('allauth.urls')), # included urlpatterns ] urlpatterns = [] @@ -392,6 +391,12 @@ if settings.ENABLE_CLASSIC_FRONTEND: frontendpatterns += classic_frontendpatterns + +# Add auth +frontendpatterns += [ + path('accounts/', include('allauth.urls')) # Always needed as we need providers +] + if settings.ENABLE_PLATFORM_FRONTEND: frontendpatterns += platform_urls if not settings.ENABLE_CLASSIC_FRONTEND: From bb40d134e14c94bb2193ccf646079189081b703c Mon Sep 17 00:00:00 2001 From: Matthias Mair Date: Tue, 25 Jun 2024 20:39:40 +0200 Subject: [PATCH 029/156] disable tests that rely on old endpoints - to be replaced --- .../InvenTree/settings/settings_js.html | 2 ++ src/backend/InvenTree/users/test_api.py | 32 +++++++++---------- src/backend/InvenTree/users/tests.py | 4 ++- 3 files changed, 21 insertions(+), 17 deletions(-) diff --git a/src/backend/InvenTree/templates/InvenTree/settings/settings_js.html b/src/backend/InvenTree/templates/InvenTree/settings/settings_js.html index 8e857739a745..b19beb0a8a35 100644 --- a/src/backend/InvenTree/templates/InvenTree/settings/settings_js.html +++ b/src/backend/InvenTree/templates/InvenTree/settings/settings_js.html @@ -82,6 +82,7 @@ ); }); +/* $("#edit-password").on('click', function() { launchModalForm( "{% url 'set-password' %}", @@ -90,3 +91,4 @@ } ); }); +*/ diff --git a/src/backend/InvenTree/users/test_api.py b/src/backend/InvenTree/users/test_api.py index 6e9805c60188..0ce6772b9cc3 100644 --- a/src/backend/InvenTree/users/test_api.py +++ b/src/backend/InvenTree/users/test_api.py @@ -48,14 +48,14 @@ def test_group_api(self): self.assertIn('name', response.data) - def test_logout(self): - """Test api logout endpoint.""" - token_key = self.get(url=reverse('api-token')).data['token'] - self.client.logout() - self.client.credentials(HTTP_AUTHORIZATION='Token ' + token_key) + # def test_logout(self): + # """Test api logout endpoint.""" + # token_key = self.get(url=reverse('api-token')).data['token'] + # self.client.logout() + # self.client.credentials(HTTP_AUTHORIZATION='Token ' + token_key) - self.post(reverse('api-logout'), expected_code=200) - self.get(reverse('api-token'), expected_code=401) + # self.post(reverse('api-logout'), expected_code=200) + # self.get(reverse('api-token'), expected_code=401) def test_login_redirect(self): """Test login redirect endpoint.""" @@ -176,12 +176,12 @@ def test_token_auth(self): self.client.get(me, expected_code=200) - def test_buildin_token(self): - """Test the built-in token authentication.""" - response = self.post( - reverse('rest_login'), - {'username': self.username, 'password': self.password}, - expected_code=200, - ) - self.assertIn('key', response.data) - self.assertTrue(response.data['key'].startswith('inv-')) + # def test_buildin_token(self): + # """Test the built-in token authentication.""" + # response = self.post( + # reverse('rest_login'), + # {'username': self.username, 'password': self.password}, + # expected_code=200, + # ) + # self.assertIn('key', response.data) + # self.assertTrue(response.data['key'].startswith('inv-')) diff --git a/src/backend/InvenTree/users/tests.py b/src/backend/InvenTree/users/tests.py index 968d9024b38d..a95e5b626063 100644 --- a/src/backend/InvenTree/users/tests.py +++ b/src/backend/InvenTree/users/tests.py @@ -274,8 +274,9 @@ def test_token(self): class MFALoginTest(InvenTreeAPITestCase): """Some simplistic tests to ensure that MFA is working.""" + """ def test_api(self): - """Test that the API is working.""" + ""Test that the API is working."" auth_data = {'username': self.username, 'password': self.password} login_url = reverse('api-login') @@ -313,3 +314,4 @@ def test_api(self): # Wrong login should not work auth_data['password'] = 'wrong' self.post(login_url, auth_data, expected_code=401) + """ From ed8dec4ff554129023fa6a00f43095323c38389c Mon Sep 17 00:00:00 2001 From: Matthias Mair Date: Tue, 25 Jun 2024 20:41:20 +0200 Subject: [PATCH 030/156] always track session changes --- src/backend/InvenTree/InvenTree/settings.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/backend/InvenTree/InvenTree/settings.py b/src/backend/InvenTree/InvenTree/settings.py index 13e43752792b..4d671aec25da 100644 --- a/src/backend/InvenTree/InvenTree/settings.py +++ b/src/backend/InvenTree/InvenTree/settings.py @@ -1132,6 +1132,7 @@ ACCOUNT_EMAIL_CONFIRMATION_EXPIRE_DAYS = get_setting( 'INVENTREE_LOGIN_CONFIRM_DAYS', 'login_confirm_days', 3, typecast=int ) +USERSESSIONS_TRACK_ACTIVITY = True # allauth rate limiting: https://docs.allauth.org/en/latest/account/rate_limits.html # The default login rate limit is "5/m/user,5/m/ip,5/m/key" From 843fdc9d664594941b8fdf94a3e2ed0d7c1fa3d1 Mon Sep 17 00:00:00 2001 From: Matthias Mair Date: Tue, 25 Jun 2024 20:55:12 +0200 Subject: [PATCH 031/156] remove old allauth templates --- .../templates/allauth_2fa/authenticate.html | 16 ------- .../templates/allauth_2fa/backup_tokens.html | 39 --------------- .../templates/allauth_2fa/remove.html | 27 ----------- .../templates/allauth_2fa/setup.html | 48 ------------------- 4 files changed, 130 deletions(-) delete mode 100644 src/backend/InvenTree/templates/allauth_2fa/authenticate.html delete mode 100644 src/backend/InvenTree/templates/allauth_2fa/backup_tokens.html delete mode 100644 src/backend/InvenTree/templates/allauth_2fa/remove.html delete mode 100644 src/backend/InvenTree/templates/allauth_2fa/setup.html diff --git a/src/backend/InvenTree/templates/allauth_2fa/authenticate.html b/src/backend/InvenTree/templates/allauth_2fa/authenticate.html deleted file mode 100644 index 891d5ab5eb21..000000000000 --- a/src/backend/InvenTree/templates/allauth_2fa/authenticate.html +++ /dev/null @@ -1,16 +0,0 @@ -{% extends "account/base.html" %} -{% load i18n crispy_forms_tags %} - -{% block content %} -

{% trans "Two-Factor Authentication" %}

- - -{% endblock content %} diff --git a/src/backend/InvenTree/templates/allauth_2fa/backup_tokens.html b/src/backend/InvenTree/templates/allauth_2fa/backup_tokens.html deleted file mode 100644 index 35cc8c5b424a..000000000000 --- a/src/backend/InvenTree/templates/allauth_2fa/backup_tokens.html +++ /dev/null @@ -1,39 +0,0 @@ -{% extends "account/base.html" %} -{% load i18n %} - -{% block content %} -

- {% trans "Two-Factor Authentication Backup Tokens" %} -

- -{% if backup_tokens %} - {% if reveal_tokens %} -
    - {% for token in backup_tokens %} -
  • {{ token.token }}
  • - {% endfor %} -
- {% else %} - {% trans 'Backup tokens have been generated, but are not revealed here for security reasons. Press the button below to generate new ones.' %} - {% endif %} -{% else %} - {% trans 'No backup tokens are available. Press the button below to generate some.' %} -{% endif %} - -
-
- {% csrf_token %} -
- -
-
- - - -{% endblock content %} diff --git a/src/backend/InvenTree/templates/allauth_2fa/remove.html b/src/backend/InvenTree/templates/allauth_2fa/remove.html deleted file mode 100644 index 67c9c05286ac..000000000000 --- a/src/backend/InvenTree/templates/allauth_2fa/remove.html +++ /dev/null @@ -1,27 +0,0 @@ -{% extends "account/base.html" %} -{% load i18n crispy_forms_tags %} - -{% block content %} -

- {% trans "Disable Two-Factor Authentication" %} -

- -

{% trans "Are you sure?" %}

- -
- {% csrf_token %} - {{ form|crispy }} -
-
- -
-
- - -{% endblock content %} diff --git a/src/backend/InvenTree/templates/allauth_2fa/setup.html b/src/backend/InvenTree/templates/allauth_2fa/setup.html deleted file mode 100644 index c05b049f1f50..000000000000 --- a/src/backend/InvenTree/templates/allauth_2fa/setup.html +++ /dev/null @@ -1,48 +0,0 @@ -{% extends "account/base.html" %} -{% load i18n crispy_forms_tags %} - -{% block content %} -

- {% trans "Setup Two-Factor Authentication" %} -

- -

- {% trans 'Step 1' %}: -

- -

- {% trans 'Scan the QR code below with a token generator of your choice (for instance Google Authenticator).' %} -

- -
-{% trans 'QR Code' %} -
-

{% trans 'Secret: ' %}{{ secret }}

-
- -

- {% trans 'Step 2' %}: -

- -

- {% trans 'Input a token generated by the app:' %} -

- -
- {% csrf_token %} - {{ form|crispy }} - -
-
- -
-
- - -{% endblock content %} From 21c349bfe8ae828addd2c7ece332fc720acd9824 Mon Sep 17 00:00:00 2001 From: Matthias Mair Date: Tue, 25 Jun 2024 21:13:34 +0200 Subject: [PATCH 032/156] remove old ref --- .../templates/InvenTree/settings/settings_js.html | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/src/backend/InvenTree/templates/InvenTree/settings/settings_js.html b/src/backend/InvenTree/templates/InvenTree/settings/settings_js.html index b19beb0a8a35..bb9c7fbd925d 100644 --- a/src/backend/InvenTree/templates/InvenTree/settings/settings_js.html +++ b/src/backend/InvenTree/templates/InvenTree/settings/settings_js.html @@ -81,14 +81,3 @@ } ); }); - -/* -$("#edit-password").on('click', function() { - launchModalForm( - "{% url 'set-password' %}", - { - reload: true, - } - ); -}); -*/ From df1c51b45e37eb58d42fca6c1b9c95a0d1a4b225 Mon Sep 17 00:00:00 2001 From: Matthias Mair Date: Tue, 25 Jun 2024 21:13:43 +0200 Subject: [PATCH 033/156] add missing model --- src/backend/InvenTree/users/models.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/backend/InvenTree/users/models.py b/src/backend/InvenTree/users/models.py index 20330a2e845e..cc8c27ad0a26 100644 --- a/src/backend/InvenTree/users/models.py +++ b/src/backend/InvenTree/users/models.py @@ -351,6 +351,8 @@ def get_ruleset_ignore(): 'error_report_error', 'exchange_rate', 'exchange_exchangebackend', + 'usersessions_usersession', + 'sessions_session', # Django-q 'django_q_ormq', 'django_q_failure', From 1367eae518760794c11045a6f5161c1076114cbf Mon Sep 17 00:00:00 2001 From: Matthias Mair Date: Tue, 25 Jun 2024 21:54:14 +0200 Subject: [PATCH 034/156] fix session lookup --- src/backend/InvenTree/InvenTree/views.py | 6 +++--- .../InvenTree/templates/InvenTree/settings/user.html | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/backend/InvenTree/InvenTree/views.py b/src/backend/InvenTree/InvenTree/views.py index 557db269c12b..d43383af5467 100644 --- a/src/backend/InvenTree/InvenTree/views.py +++ b/src/backend/InvenTree/InvenTree/views.py @@ -479,9 +479,9 @@ def get_context_data(self, **kwargs): # user db sessions ctx['session_key'] = self.request.session.session_key - ctx['session_list'] = self.request.user.session_set.filter( - expire_date__gt=now() - ).order_by('-last_activity') + ctx['session_list'] = self.request.user.usersession_set.order_by( + '-last_seen_at' + ) return ctx diff --git a/src/backend/InvenTree/templates/InvenTree/settings/user.html b/src/backend/InvenTree/templates/InvenTree/settings/user.html index eb3a451c4cc8..d621b3d5b325 100644 --- a/src/backend/InvenTree/templates/InvenTree/settings/user.html +++ b/src/backend/InvenTree/templates/InvenTree/settings/user.html @@ -200,9 +200,9 @@

{% trans "Active Sessions" %}

{% endif %} {% if object.session_key == session_key %} - {% blocktrans with time=object.last_activity|timesince %}{{ time }} ago (this session){% endblocktrans %} + {% blocktrans with time=object.last_seen_at|timesince %}{{ time }} ago (this session){% endblocktrans %} {% else %} - {% blocktrans with time=object.last_activity|timesince %}{{ time }} ago{% endblocktrans %} + {% blocktrans with time=object.last_seen_at|timesince %}{{ time }} ago{% endblocktrans %} {% endif %} From 42b3f0c453c30579d46a50b72744a706380f1c2d Mon Sep 17 00:00:00 2001 From: Matthias Mair Date: Tue, 25 Jun 2024 22:02:52 +0200 Subject: [PATCH 035/156] always logout when pwd is changed --- src/backend/InvenTree/InvenTree/settings.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/backend/InvenTree/InvenTree/settings.py b/src/backend/InvenTree/InvenTree/settings.py index 4d671aec25da..58471064fdf4 100644 --- a/src/backend/InvenTree/InvenTree/settings.py +++ b/src/backend/InvenTree/InvenTree/settings.py @@ -1179,6 +1179,7 @@ SOCIALACCOUNT_ADAPTER = 'InvenTree.forms.CustomSocialAccountAdapter' ACCOUNT_ADAPTER = 'InvenTree.forms.CustomAccountAdapter' +ACCOUNT_LOGOUT_ON_PASSWORD_CHANGE = True HEADLESS_FRONTEND_URLS = { 'account_confirm_email': 'https://app.project.org/account/verify-email/{key}', From 8fad7b1e1fbd3ffc7355fdb9c8ebee757d45efce Mon Sep 17 00:00:00 2001 From: Matthias Mair Date: Wed, 26 Jun 2024 12:10:09 +0200 Subject: [PATCH 036/156] reimplement session ending --- src/backend/InvenTree/InvenTree/urls.py | 17 +++++++++++------ src/backend/InvenTree/InvenTree/views.py | 10 ++++++++++ .../templates/InvenTree/settings/user.html | 2 +- 3 files changed, 22 insertions(+), 7 deletions(-) diff --git a/src/backend/InvenTree/InvenTree/urls.py b/src/backend/InvenTree/InvenTree/urls.py index 99b9814d9919..62ded2c50f8c 100644 --- a/src/backend/InvenTree/InvenTree/urls.py +++ b/src/backend/InvenTree/InvenTree/urls.py @@ -47,6 +47,7 @@ AppearanceSelectView, CustomConnectionsView, CustomEmailView, + CustomListUserSessionsView, CustomLoginView, CustomPasswordResetFromKeyView, DatabaseStatsView, @@ -356,6 +357,12 @@ path('about/', AboutView.as_view(), name='about'), path('stats/', DatabaseStatsView.as_view(), name='stats'), # Single Sign On / allauth + path( + 'accounts/sessions/', + view=CustomListUserSessionsView.as_view(), + name='usersessions_list', + ), + path('accounts/', include('allauth.urls')), # overrides of urlpatterns path('accounts/email/', CustomEmailView.as_view(), name='account_email'), path( @@ -392,21 +399,19 @@ if settings.ENABLE_CLASSIC_FRONTEND: frontendpatterns += classic_frontendpatterns -# Add auth -frontendpatterns += [ - path('accounts/', include('allauth.urls')) # Always needed as we need providers -] - if settings.ENABLE_PLATFORM_FRONTEND: frontendpatterns += platform_urls if not settings.ENABLE_CLASSIC_FRONTEND: # Add a redirect for login views frontendpatterns += [ + path( + 'accounts/', include('allauth.urls') + ), # Still needed for provider login path( 'accounts/login/', RedirectView.as_view(url=settings.FRONTEND_URL_BASE, permanent=False), name='account_login', - ) + ), ] urlpatterns += frontendpatterns diff --git a/src/backend/InvenTree/InvenTree/views.py b/src/backend/InvenTree/InvenTree/views.py index d43383af5467..c428be33352b 100644 --- a/src/backend/InvenTree/InvenTree/views.py +++ b/src/backend/InvenTree/InvenTree/views.py @@ -20,6 +20,7 @@ from allauth.account.views import EmailView, LoginView, PasswordResetFromKeyView from allauth.socialaccount.forms import DisconnectForm from allauth.socialaccount.views import ConnectionsView +from allauth.usersessions.views import ListUserSessionsView from djmoney.contrib.exchange.models import ExchangeBackend, Rate import common.currency @@ -582,6 +583,15 @@ class AboutView(AjaxView): ajax_form_title = _('About InvenTree') +class CustomListUserSessionsView(ListUserSessionsView): + """A view to delete all other sessions.""" + + def form_valid(self, form): + """Delete all other sessions.""" + super().form_valid(form) + return HttpResponseRedirect(reverse_lazy('settings')) + + class NotificationsView(TemplateView): """View for showing notifications.""" diff --git a/src/backend/InvenTree/templates/InvenTree/settings/user.html b/src/backend/InvenTree/templates/InvenTree/settings/user.html index d621b3d5b325..aa06bf3e60f6 100644 --- a/src/backend/InvenTree/templates/InvenTree/settings/user.html +++ b/src/backend/InvenTree/templates/InvenTree/settings/user.html @@ -168,7 +168,7 @@

{% trans "Active Sessions" %}

{% include "spacer.html" %}
{% if session_list.count > 1 %} -
+ {% csrf_token %} + + + + + ); +} diff --git a/src/frontend/src/router.tsx b/src/frontend/src/router.tsx index c444f9afeb25..eb0dad939cb5 100644 --- a/src/frontend/src/router.tsx +++ b/src/frontend/src/router.tsx @@ -104,6 +104,7 @@ export const NotFound = Loadable( lazy(() => import('./components/errors/NotFound')) ); export const Login = Loadable(lazy(() => import('./pages/Auth/Login'))); +export const MFALogin = Loadable(lazy(() => import('./pages/Auth/MFALogin'))); export const Logout = Loadable(lazy(() => import('./pages/Auth/Logout'))); export const Logged_In = Loadable(lazy(() => import('./pages/Auth/Logged-In'))); export const Reset = Loadable(lazy(() => import('./pages/Auth/Reset'))); @@ -165,6 +166,7 @@ export const routes = ( }> } />, + } />, } />, } /> } /> diff --git a/src/frontend/src/states/UserState.tsx b/src/frontend/src/states/UserState.tsx index 8ba1abc88b07..c5f70942869e 100644 --- a/src/frontend/src/states/UserState.tsx +++ b/src/frontend/src/states/UserState.tsx @@ -16,6 +16,8 @@ export interface UserStateProps { setUser: (newUser: UserProps) => void; setToken: (newToken: string) => void; clearToken: () => void; + session: string | undefined; + setSession: (newSession: string) => void; fetchUserToken: () => void; fetchUserState: () => void; clearUserState: () => void; @@ -51,6 +53,10 @@ export const useUserState = create((set, get) => ({ set({ token: undefined }); setApiDefaults(); }, + session: undefined, + setSession: (newSession: string) => { + set({ session: newSession }); + }, userId: () => { const user: UserProps = get().user as UserProps; return user.pk; From d701182d520e50205faa307c647bf3a7cc6b556e Mon Sep 17 00:00:00 2001 From: Matthias Mair Date: Thu, 26 Dec 2024 18:10:01 +0100 Subject: [PATCH 045/156] re-implement logoff --- src/frontend/src/enums/ApiEndpoints.tsx | 2 +- src/frontend/src/functions/auth.tsx | 11 +++++++++-- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/src/frontend/src/enums/ApiEndpoints.tsx b/src/frontend/src/enums/ApiEndpoints.tsx index da84bf8872a2..b9308a4085f4 100644 --- a/src/frontend/src/enums/ApiEndpoints.tsx +++ b/src/frontend/src/enums/ApiEndpoints.tsx @@ -26,7 +26,7 @@ export enum ApiEndpoints { user_email_primary = 'auth/emails/:id/primary/', user_login = '_allauth/app/v1/auth/login', user_login_mfa = '_allauth/app/v1/auth/2fa/authenticate', - user_logout = 'auth/logout/', + user_logout = '_allauth/app/v1/auth/session', user_register = 'auth/registration/', // Generic API endpoints diff --git a/src/frontend/src/functions/auth.tsx b/src/frontend/src/functions/auth.tsx index b0aee895959d..ad28f0919e3a 100644 --- a/src/frontend/src/functions/auth.tsx +++ b/src/frontend/src/functions/auth.tsx @@ -91,6 +91,7 @@ export const doBasicLogin = async ( ) .then((response) => { if (response.status == 200 && response.data?.meta?.is_authenticated) { + setSession(response.data.meta.session_token); setToken(response.data.meta.access_token); result = true; } @@ -121,11 +122,16 @@ export const doBasicLogin = async ( * @arg deleteToken: If true, delete the token from the server */ export const doLogout = async (navigate: NavigateFunction) => { - const { clearUserState, isLoggedIn } = useUserState.getState(); + const { clearUserState, isLoggedIn, setSession } = useUserState.getState(); + const { session } = useUserState.getState(); // Logout from the server session if (isLoggedIn() || !!getCsrfCookie()) { - await api.post(apiUrl(ApiEndpoints.user_logout)).catch(() => {}); + await api + .delete(apiUrl(ApiEndpoints.user_logout), { + headers: { 'X-Session-Token': session } + }) + .catch(() => {}); showLoginNotification({ title: t`Logged Out`, @@ -133,6 +139,7 @@ export const doLogout = async (navigate: NavigateFunction) => { }); } + setSession(undefined); clearUserState(); clearCsrfCookie(); navigate('/login'); From 7bfdc86799d7a0790614b1b580e1463a407f2bdf Mon Sep 17 00:00:00 2001 From: Matthias Mair Date: Thu, 26 Dec 2024 18:10:52 +0100 Subject: [PATCH 046/156] stop failure message from appearing when in MFA flow --- .../src/components/forms/AuthenticationForm.tsx | 4 +++- src/frontend/src/functions/auth.tsx | 10 +++++++--- src/frontend/src/states/UserState.tsx | 4 ++-- 3 files changed, 12 insertions(+), 6 deletions(-) diff --git a/src/frontend/src/components/forms/AuthenticationForm.tsx b/src/frontend/src/components/forms/AuthenticationForm.tsx index 4a144986ce8a..71b99f657317 100644 --- a/src/frontend/src/components/forms/AuthenticationForm.tsx +++ b/src/frontend/src/components/forms/AuthenticationForm.tsx @@ -50,7 +50,7 @@ export function AuthenticationForm() { classicForm.values.password, navigate ) - .then(() => { + .then((success) => { setIsLoggingIn(false); if (isLoggedIn()) { @@ -59,6 +59,8 @@ export function AuthenticationForm() { message: t`Logged in successfully` }); followRedirect(navigate, location?.state); + } else if (success) { + // MFA login } else { showLoginNotification({ title: t`Login failed`, diff --git a/src/frontend/src/functions/auth.tsx b/src/frontend/src/functions/auth.tsx index ad28f0919e3a..60ce2d794957 100644 --- a/src/frontend/src/functions/auth.tsx +++ b/src/frontend/src/functions/auth.tsx @@ -75,7 +75,8 @@ export const doBasicLogin = async ( const login_url = apiUrl(ApiEndpoints.user_login); - let result = false; + let loginDone = false; + let success = false; // Attempt login with await api @@ -93,7 +94,8 @@ export const doBasicLogin = async ( if (response.status == 200 && response.data?.meta?.is_authenticated) { setSession(response.data.meta.session_token); setToken(response.data.meta.access_token); - result = true; + loginDone = true; + success = true; } }) .catch((err) => { @@ -103,17 +105,19 @@ export const doBasicLogin = async ( ); if (mfa_flow && mfa_flow.is_pending == true) { setSession(err.response.data.meta.session_token); + success = true; navigate('/mfa'); } } }); - if (result) { + if (loginDone) { await fetchUserState(); fetchGlobalStates(); } else { clearUserState(); } + return success; }; /** diff --git a/src/frontend/src/states/UserState.tsx b/src/frontend/src/states/UserState.tsx index c5f70942869e..965de1f6f5f0 100644 --- a/src/frontend/src/states/UserState.tsx +++ b/src/frontend/src/states/UserState.tsx @@ -17,7 +17,7 @@ export interface UserStateProps { setToken: (newToken: string) => void; clearToken: () => void; session: string | undefined; - setSession: (newSession: string) => void; + setSession: (newSession: string | undefined) => void; fetchUserToken: () => void; fetchUserState: () => void; clearUserState: () => void; @@ -54,7 +54,7 @@ export const useUserState = create((set, get) => ({ setApiDefaults(); }, session: undefined, - setSession: (newSession: string) => { + setSession: (newSession: string | undefined) => { set({ session: newSession }); }, userId: () => { From 2a77e0b0f92695e0fb3cb787a9ff8f28429bfbdb Mon Sep 17 00:00:00 2001 From: Matthias Mair Date: Thu, 26 Dec 2024 18:13:01 +0100 Subject: [PATCH 047/156] remove jwt mention --- src/backend/InvenTree/config_template.yaml | 5 ----- 1 file changed, 5 deletions(-) diff --git a/src/backend/InvenTree/config_template.yaml b/src/backend/InvenTree/config_template.yaml index 7dcdde460da5..fdb540a9bdda 100644 --- a/src/backend/InvenTree/config_template.yaml +++ b/src/backend/InvenTree/config_template.yaml @@ -174,11 +174,6 @@ login_default_protocol: http remote_login_enabled: False remote_login_header: HTTP_REMOTE_USER -# JWT tokens -# JWT can be used optionally to authenticate users. Turned off by default. -# Alternatively, use the environment variable INVENTREE_USE_JWT -# use_jwt: True - # Logout redirect configuration # This setting may be required if using remote / proxy login to redirect requests # during the logout process (default is 'index'). Please read the docs for more details From c003084690c539629dc3adaa9eb6b7d1487866e3 Mon Sep 17 00:00:00 2001 From: Matthias Mair Date: Sat, 28 Dec 2024 12:05:24 +0100 Subject: [PATCH 048/156] fix: email endpoints (to be cleaned TODO@matmair) --- src/frontend/src/enums/ApiEndpoints.tsx | 5 +- .../AccountSettings/SecurityContent.tsx | 55 +++++++++++++------ 2 files changed, 40 insertions(+), 20 deletions(-) diff --git a/src/frontend/src/enums/ApiEndpoints.tsx b/src/frontend/src/enums/ApiEndpoints.tsx index c230c5cb45b8..1fa858f054e1 100644 --- a/src/frontend/src/enums/ApiEndpoints.tsx +++ b/src/frontend/src/enums/ApiEndpoints.tsx @@ -21,10 +21,7 @@ export enum ApiEndpoints { user_change_password = 'auth/password/change/', user_sso = 'auth/social/', user_sso_remove = 'auth/social/:id/disconnect/', - user_emails = 'auth/emails/', - user_email_remove = 'auth/emails/:id/remove/', - user_email_verify = 'auth/emails/:id/verify/', - user_email_primary = 'auth/emails/:id/primary/', + user_emails = '_allauth/app/v1/account/email', user_login = '_allauth/app/v1/auth/login', user_login_mfa = '_allauth/app/v1/auth/2fa/authenticate', user_logout = '_allauth/app/v1/auth/session', diff --git a/src/frontend/src/pages/Index/Settings/AccountSettings/SecurityContent.tsx b/src/frontend/src/pages/Index/Settings/AccountSettings/SecurityContent.tsx index 089ee7102548..814896d8a0b0 100644 --- a/src/frontend/src/pages/Index/Settings/AccountSettings/SecurityContent.tsx +++ b/src/frontend/src/pages/Index/Settings/AccountSettings/SecurityContent.tsx @@ -106,20 +106,45 @@ function EmailContent() { api.get(apiUrl(ApiEndpoints.user_emails)).then((res) => res.data) }); - function runServerAction(url: ApiEndpoints) { + function runServerAction( + url: ApiEndpoints, + action: 'post' | 'put' | 'delete' = 'post' + ) { + let act: any; + switch (action) { + case 'post': + act = api.post; + break; + case 'put': + act = api.put; + break; + case 'delete': + act = api.delete; + break; + } + act(apiUrl(url), { email: value }) + .then(() => { + refetch(); + }) + .catch((res: any) => console.log(res.data)); + } + + function addEmail() { api - .post(apiUrl(url, undefined, { id: value }), {}) + .post(apiUrl(ApiEndpoints.user_emails), { + email: newEmailValue + }) .then(() => { refetch(); }) .catch((res) => console.log(res.data)); } - function addEmail() { + function changePrimary() { api .post(apiUrl(ApiEndpoints.user_emails), { - email: newEmailValue, - user: user?.pk + email: value, + primary: true }) .then(() => { refetch(); @@ -139,19 +164,19 @@ function EmailContent() { label={t`The following email addresses are associated with your account:`} > - {data.map((link: any) => ( + {data.map((email: any) => ( - {link.email} - {link.primary && ( + {email.email} + {email.primary && ( Primary )} - {link.verified ? ( + {email.verified ? ( Verified @@ -183,18 +208,16 @@ function EmailContent() { - From 4c99f3dcbf932b4532aa03530e4fbbc9d8677fa5 Mon Sep 17 00:00:00 2001 From: Matthias Mair Date: Sat, 28 Dec 2024 12:56:39 +0100 Subject: [PATCH 049/156] remove unused endpoints --- src/backend/InvenTree/InvenTree/urls.py | 26 ------------------------- 1 file changed, 26 deletions(-) diff --git a/src/backend/InvenTree/InvenTree/urls.py b/src/backend/InvenTree/InvenTree/urls.py index 0f0aa73943fe..41f2f6d93a6b 100644 --- a/src/backend/InvenTree/InvenTree/urls.py +++ b/src/backend/InvenTree/InvenTree/urls.py @@ -105,32 +105,6 @@ # path( # 'providers/', SocialProviderListView.as_view(), name='social_providers' # ), - # path( - # 'emails/', - # include([ - # path( - # '/', - # include([ - # path( - # 'primary/', - # EmailPrimaryView.as_view(), - # name='email-primary', - # ), - # path( - # 'verify/', - # EmailVerifyView.as_view(), - # name='email-verify', - # ), - # path( - # 'remove/', - # EmailRemoveView().as_view(), - # name='email-remove', - # ), - # ]), - # ), - # path('', EmailListView.as_view(), name='email-list'), - # ]), - # ), # path('social/', include(get_provider_urls())), # path( # 'social/', SocialAccountListView.as_view(), name='social_account_list' From a584334a24baf56909ba1b4b7d0f1c275e82a328 Mon Sep 17 00:00:00 2001 From: Matthias Mair Date: Sat, 28 Dec 2024 13:19:05 +0100 Subject: [PATCH 050/156] ignore the now often-used 410 error --- src/backend/InvenTree/InvenTree/settings.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/backend/InvenTree/InvenTree/settings.py b/src/backend/InvenTree/InvenTree/settings.py index fc3c0106ef41..eecce31528d2 100644 --- a/src/backend/InvenTree/InvenTree/settings.py +++ b/src/backend/InvenTree/InvenTree/settings.py @@ -17,7 +17,7 @@ import django.conf.locale import django.core.exceptions from django.core.validators import URLValidator -from django.http import Http404 +from django.http import Http404, HttpResponseGone import structlog from corsheaders.defaults import default_headers @@ -1340,7 +1340,7 @@ } # Ignore these error typeps for in-database error logging -IGNORED_ERRORS = [Http404, django.core.exceptions.PermissionDenied] +IGNORED_ERRORS = [Http404, HttpResponseGone, django.core.exceptions.PermissionDenied] # Maintenance mode MAINTENANCE_MODE_RETRY_AFTER = 10 From ef14310fc48f65f876889f9a5d5a61fab3a080c0 Mon Sep 17 00:00:00 2001 From: Matthias Mair Date: Sat, 28 Dec 2024 17:57:13 +0100 Subject: [PATCH 051/156] fix auth for email actions in MFA scenarios --- src/frontend/src/functions/auth.tsx | 3 +- .../AccountSettings/SecurityContent.tsx | 110 +++++++++++------- 2 files changed, 68 insertions(+), 45 deletions(-) diff --git a/src/frontend/src/functions/auth.tsx b/src/frontend/src/functions/auth.tsx index 60ce2d794957..b09526f2d4b0 100644 --- a/src/frontend/src/functions/auth.tsx +++ b/src/frontend/src/functions/auth.tsx @@ -201,7 +201,7 @@ export function handleMfaLogin( location: Location, values: { code: string } ) { - const { session, setToken } = useUserState.getState(); + const { session, setToken, setSession } = useUserState.getState(); api .post( @@ -212,6 +212,7 @@ export function handleMfaLogin( { headers: { 'X-Session-Token': session } } ) .then((response) => { + setSession(response.data.meta.session_token); setToken(response.data.meta.access_token); followRedirect(navigate, location?.state); }); diff --git a/src/frontend/src/pages/Index/Settings/AccountSettings/SecurityContent.tsx b/src/frontend/src/pages/Index/Settings/AccountSettings/SecurityContent.tsx index 814896d8a0b0..09ab73141a1b 100644 --- a/src/frontend/src/pages/Index/Settings/AccountSettings/SecurityContent.tsx +++ b/src/frontend/src/pages/Index/Settings/AccountSettings/SecurityContent.tsx @@ -99,11 +99,15 @@ export function SecurityContent() { function EmailContent() { const [value, setValue] = useState(''); const [newEmailValue, setNewEmailValue] = useState(''); - const [user] = useUserState((state) => [state.user]); + const [session] = useUserState((state) => [state.session]); const { isLoading, data, refetch } = useQuery({ queryKey: ['emails'], queryFn: () => - api.get(apiUrl(ApiEndpoints.user_emails)).then((res) => res.data) + api + .get(apiUrl(ApiEndpoints.user_emails), { + headers: { 'X-Session-Token': session } + }) + .then((res) => res.data.data) }); function runServerAction( @@ -122,7 +126,11 @@ function EmailContent() { act = api.delete; break; } - act(apiUrl(url), { email: value }) + act( + apiUrl(url), + { email: value }, + { headers: { 'X-Session-Token': session } } + ) .then(() => { refetch(); }) @@ -131,9 +139,13 @@ function EmailContent() { function addEmail() { api - .post(apiUrl(ApiEndpoints.user_emails), { - email: newEmailValue - }) + .post( + apiUrl(ApiEndpoints.user_emails), + { + email: newEmailValue + }, + { headers: { 'X-Session-Token': session } } + ) .then(() => { refetch(); }) @@ -142,10 +154,14 @@ function EmailContent() { function changePrimary() { api - .post(apiUrl(ApiEndpoints.user_emails), { - email: value, - primary: true - }) + .post( + apiUrl(ApiEndpoints.user_emails), + { + email: value, + primary: true + }, + { headers: { 'X-Session-Token': session } } + ) .then(() => { refetch(); }) @@ -157,40 +173,46 @@ function EmailContent() { return ( - - - {data.map((email: any) => ( - - {email.email} - {email.primary && ( - - Primary - - )} - {email.verified ? ( - - Verified - - ) : ( - - Unverified - - )} - - } - /> - ))} - - + {data.length == 0 ? ( + + Currently no emails are registered + + ) : ( + + + {data.map((email: any) => ( + + {email.email} + {email.primary && ( + + Primary + + )} + {email.verified ? ( + + Verified + + ) : ( + + Unverified + + )} + + } + /> + ))} + + + )} From 5b49667797e7c8d45605d9b934d30abdf8bf2569 Mon Sep 17 00:00:00 2001 From: Matthias Mair Date: Sun, 29 Dec 2024 01:00:47 +0100 Subject: [PATCH 052/156] add mfa listing use build-in forms --- src/frontend/src/enums/ApiEndpoints.tsx | 1 + .../AccountSettings/SecurityContent.tsx | 29 ++++++++----------- src/frontend/src/states/ApiState.tsx | 8 ++--- src/frontend/src/states/states.tsx | 20 +++++++++++++ 4 files changed, 37 insertions(+), 21 deletions(-) diff --git a/src/frontend/src/enums/ApiEndpoints.tsx b/src/frontend/src/enums/ApiEndpoints.tsx index 1fa858f054e1..23231f6ee4e8 100644 --- a/src/frontend/src/enums/ApiEndpoints.tsx +++ b/src/frontend/src/enums/ApiEndpoints.tsx @@ -50,6 +50,7 @@ export enum ApiEndpoints { icons = 'icons/', selectionlist_list = 'selection/', selectionlist_detail = 'selection/:id/', + securtiy_settings = '_allauth/app/v1/config', // Barcode API endpoints barcode = 'barcode/', diff --git a/src/frontend/src/pages/Index/Settings/AccountSettings/SecurityContent.tsx b/src/frontend/src/pages/Index/Settings/AccountSettings/SecurityContent.tsx index 09ab73141a1b..ce7aa1b53301 100644 --- a/src/frontend/src/pages/Index/Settings/AccountSettings/SecurityContent.tsx +++ b/src/frontend/src/pages/Index/Settings/AccountSettings/SecurityContent.tsx @@ -68,24 +68,19 @@ export function SecurityContent() { <Trans>Multifactor</Trans> - {isLoadingProvider ? ( - + + {isMfaEnabled ? ( + ) : ( - <> - {isMfaEnabled ? ( - - ) : ( - } - title={t`Not enabled`} - color='yellow' - > - - Multifactor authentication is not configured for your account{' '} - - - )} - + } + title={t`Not enabled`} + color='yellow' + > + + Multifactor authentication is not configured for your account{' '} + + )} diff --git a/src/frontend/src/states/ApiState.tsx b/src/frontend/src/states/ApiState.tsx index 60431112610f..82989dd1373a 100644 --- a/src/frontend/src/states/ApiState.tsx +++ b/src/frontend/src/states/ApiState.tsx @@ -4,13 +4,13 @@ import { createJSONStorage, persist } from 'zustand/middleware'; import { api } from '../App'; import { emptyServerAPI } from '../defaults/defaults'; import { ApiEndpoints } from '../enums/ApiEndpoints'; -import type { AuthProps, ServerAPIProps } from './states'; +import type { SecuritySetting, ServerAPIProps } from './states'; interface ServerApiStateProps { server: ServerAPIProps; setServer: (newServer: ServerAPIProps) => void; fetchServerApiState: () => void; - auth_settings?: AuthProps; + auth_settings?: SecuritySetting; } export const useServerApiState = create<ServerApiStateProps>()( @@ -31,11 +31,11 @@ export const useServerApiState = create<ServerApiStateProps>()( // Fetch login/SSO behaviour await api - .get(apiUrl(ApiEndpoints.sso_providers), { + .get(apiUrl(ApiEndpoints.securtiy_settings), { headers: { Authorization: '' } }) .then((response) => { - set({ auth_settings: response.data }); + set({ auth_settings: response.data.data }); }) .catch(() => { console.error('ERR: Error fetching SSO information'); diff --git a/src/frontend/src/states/states.tsx b/src/frontend/src/states/states.tsx index e0ca5809300f..47f0df3de5c5 100644 --- a/src/frontend/src/states/states.tsx +++ b/src/frontend/src/states/states.tsx @@ -59,6 +59,26 @@ export interface AuthProps { password_forgotten_enabled: boolean; } +export interface SecuritySetting { + account: { + authentication_method: string; + }; + socialaccount: { providers: Provider[] }; + mfa: { + supported_types: string[]; + }; + usersessions: { + track_activity: boolean; + }; +} + +export interface Provider { + id: string; + name: string; + flows: string[]; + client_id: string; +} + export interface Provider { id: string; name: string; From 3f4239fc6ce75eb9ef4b31b379cfb91b62aa1c34 Mon Sep 17 00:00:00 2001 From: Matthias Mair <code@mjmair.com> Date: Sun, 29 Dec 2024 01:01:29 +0100 Subject: [PATCH 053/156] add dummy entry for missing frontend urls; see TODO@matmair --- src/backend/InvenTree/InvenTree/settings.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/backend/InvenTree/InvenTree/settings.py b/src/backend/InvenTree/InvenTree/settings.py index eecce31528d2..725232bfb143 100644 --- a/src/backend/InvenTree/InvenTree/settings.py +++ b/src/backend/InvenTree/InvenTree/settings.py @@ -1290,10 +1290,12 @@ ACCOUNT_ADAPTER = 'InvenTree.auth_overrides.CustomAccountAdapter' ACCOUNT_LOGOUT_ON_PASSWORD_CHANGE = True +# TDOO: Implement dynamic lookup for those HEADLESS_FRONTEND_URLS = { - 'account_confirm_email': 'https://app.project.org/account/verify-email/{key}', # noqa: RUF027 - 'account_reset_password_from_key': 'https://app.org/account/password/reset/key/{key}', # noqa: RUF027 - 'account_signup': 'https://app.org/account/signup', + 'account_confirm_email': 'http://localhost:8000/verify-email/{key}', # noqa: RUF027 + 'account_reset_password': 'http://localhost:8000/password-reset', + 'account_reset_password_from_key': 'http://localhost:8000/password-reset-key/{key}', # noqa: RUF027 + 'account_signup': 'http://localhost:8000/signup', } HEADLESS_ONLY = True HEADLESS_TOKEN_STRATEGY = 'InvenTree.auth_overrides.DRFTokenStrategy' From f49a687c54fda6a53b2511bd0103bf2fe7d4e6c4 Mon Sep 17 00:00:00 2001 From: Matthias Mair <code@mjmair.com> Date: Sun, 29 Dec 2024 01:02:06 +0100 Subject: [PATCH 054/156] remove unneeded change of confirm url --- .../InvenTree/InvenTree/auth_overrides.py | 16 ++-------------- 1 file changed, 2 insertions(+), 14 deletions(-) diff --git a/src/backend/InvenTree/InvenTree/auth_overrides.py b/src/backend/InvenTree/InvenTree/auth_overrides.py index 09f89d68e55a..62778ff52851 100644 --- a/src/backend/InvenTree/InvenTree/auth_overrides.py +++ b/src/backend/InvenTree/InvenTree/auth_overrides.py @@ -160,17 +160,7 @@ def save_user(self, request, user, form, commit=True): return user -class CustomUrlMixin: - """Mixin to set urls.""" - - def get_email_confirmation_url(self, request, emailconfirmation): - """Custom email confirmation (activation) url.""" - url = reverse('account_confirm_email', args=[emailconfirmation.key]) - - return InvenTree.helpers_model.construct_absolute_url(url) - - -class CustomAccountAdapter(CustomUrlMixin, RegistrationMixin, DefaultAccountAdapter): +class CustomAccountAdapter(RegistrationMixin, DefaultAccountAdapter): """Override of adapter to use dynamic settings.""" def send_mail(self, template_prefix, email, context): @@ -195,9 +185,7 @@ def get_email_confirmation_url(self, request, emailconfirmation): return url -class CustomSocialAccountAdapter( - CustomUrlMixin, RegistrationMixin, DefaultSocialAccountAdapter -): +class CustomSocialAccountAdapter(RegistrationMixin, DefaultSocialAccountAdapter): """Override of adapter to use dynamic settings.""" def is_auto_signup_allowed(self, request, sociallogin): From e5f6f3b741c4af5f2604f1487b3641798b534139 Mon Sep 17 00:00:00 2001 From: Matthias Mair <code@mjmair.com> Date: Sun, 29 Dec 2024 01:02:30 +0100 Subject: [PATCH 055/156] add mfa reg endpoint (not fully implemented) --- src/frontend/src/enums/ApiEndpoints.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/frontend/src/enums/ApiEndpoints.tsx b/src/frontend/src/enums/ApiEndpoints.tsx index 23231f6ee4e8..180b6f26d177 100644 --- a/src/frontend/src/enums/ApiEndpoints.tsx +++ b/src/frontend/src/enums/ApiEndpoints.tsx @@ -21,11 +21,12 @@ export enum ApiEndpoints { user_change_password = 'auth/password/change/', user_sso = 'auth/social/', user_sso_remove = 'auth/social/:id/disconnect/', - user_emails = '_allauth/app/v1/account/email', user_login = '_allauth/app/v1/auth/login', user_login_mfa = '_allauth/app/v1/auth/2fa/authenticate', user_logout = '_allauth/app/v1/auth/session', user_register = 'auth/registration/', + user_mfa = '_allauth/app/v1/account/authenticators', + user_emails = '_allauth/app/v1/account/email', // Generic API endpoints currency_list = 'currency/exchange/', From 2e6ba4de915fa4a09a4af96dd01db95ae5eab7c9 Mon Sep 17 00:00:00 2001 From: Matthias Mair <code@mjmair.com> Date: Tue, 7 Jan 2025 16:56:43 +0100 Subject: [PATCH 056/156] implement more provider stuff --- src/backend/InvenTree/InvenTree/settings.py | 1 + .../src/components/buttons/SSOButton.tsx | 37 +------- .../components/forms/AuthenticationForm.tsx | 41 ++++---- src/frontend/src/enums/ApiEndpoints.tsx | 8 +- src/frontend/src/functions/auth.tsx | 19 +++- .../AccountSettings/SecurityContent.tsx | 93 ++++++++----------- src/frontend/src/states/ApiState.tsx | 30 +++++- src/frontend/src/states/states.tsx | 18 ---- 8 files changed, 118 insertions(+), 129 deletions(-) diff --git a/src/backend/InvenTree/InvenTree/settings.py b/src/backend/InvenTree/InvenTree/settings.py index ec4e2458f535..a1053b16aa7e 100644 --- a/src/backend/InvenTree/InvenTree/settings.py +++ b/src/backend/InvenTree/InvenTree/settings.py @@ -1314,6 +1314,7 @@ 'account_reset_password': 'http://localhost:8000/password-reset', 'account_reset_password_from_key': 'http://localhost:8000/password-reset-key/{key}', # noqa: RUF027 'account_signup': 'http://localhost:8000/signup', + 'socialaccount_login_error': 'http://localhost:8000/social-login-error', } HEADLESS_ONLY = True HEADLESS_TOKEN_STRATEGY = 'InvenTree.auth_overrides.DRFTokenStrategy' diff --git a/src/frontend/src/components/buttons/SSOButton.tsx b/src/frontend/src/components/buttons/SSOButton.tsx index fc136768f775..ba7e8cb44ba4 100644 --- a/src/frontend/src/components/buttons/SSOButton.tsx +++ b/src/frontend/src/components/buttons/SSOButton.tsx @@ -15,10 +15,7 @@ import { } from '@tabler/icons-react'; import { t } from '@lingui/macro'; -import { showNotification } from '@mantine/notifications'; -import { api } from '../../App'; -import { ApiEndpoints } from '../../enums/ApiEndpoints'; -import { apiUrl } from '../../states/ApiState'; +import { ProviderLogin } from '../../functions/auth'; import type { Provider } from '../../states/states'; const brandIcons: { [key: string]: JSX.Element } = { @@ -36,43 +33,17 @@ const brandIcons: { [key: string]: JSX.Element } = { }; export function SsoButton({ provider }: Readonly<{ provider: Provider }>) { - function login() { - // set preferred provider - api - .put( - apiUrl(ApiEndpoints.ui_preference), - { preferred_method: 'pui' }, - { headers: { Authorization: '' } } - ) - .then(() => { - // redirect to login - window.location.href = provider.login; - }) - .catch(() => { - showNotification({ - title: t`Error`, - message: t`Sign in redirect failed.`, - color: 'red' - }); - }); - } - return ( <Tooltip - label={ - provider.login - ? t`You will be redirected to the provider for further actions.` - : t`This provider is not full set up.` - } + label={t`You will be redirected to the provider for further actions.`} > <Button leftSection={getBrandIcon(provider)} radius='xl' component='a' - onClick={login} - disabled={!provider.login} + onClick={() => ProviderLogin(provider)} > - {provider.display_name} + {provider.name} </Button> </Tooltip> ); diff --git a/src/frontend/src/components/forms/AuthenticationForm.tsx b/src/frontend/src/components/forms/AuthenticationForm.tsx index 71b99f657317..0335b76943be 100644 --- a/src/frontend/src/components/forms/AuthenticationForm.tsx +++ b/src/frontend/src/components/forms/AuthenticationForm.tsx @@ -34,7 +34,12 @@ export function AuthenticationForm() { }); const simpleForm = useForm({ initialValues: { email: '' } }); const [classicLoginMode, setMode] = useDisclosure(true); - const [auth_settings] = useServerApiState((state) => [state.auth_settings]); + const [auth_settings, sso_enabled, password_forgotten_enabled] = + useServerApiState((state) => [ + state.auth_settings, + state.sso_enabled, + state.password_forgotten_enabled + ]); const navigate = useNavigate(); const location = useLocation(); const { isLoggedIn } = useUserState(); @@ -98,10 +103,10 @@ export function AuthenticationForm() { return ( <> - {auth_settings?.sso_enabled === true ? ( + {sso_enabled() ? ( <> <Group grow mb='md' mt='md'> - {auth_settings.providers.map((provider) => ( + {auth_settings?.socialaccount.providers.map((provider) => ( <SsoButton provider={provider} key={provider.id} /> ))} </Group> @@ -130,7 +135,7 @@ export function AuthenticationForm() { placeholder={t`Your password`} {...classicForm.getInputProps('password')} /> - {auth_settings?.password_forgotten_enabled === true && ( + {password_forgotten_enabled() === true && ( <Group justify='space-between' mt='0'> <Anchor component='button' @@ -194,7 +199,12 @@ export function RegistrationForm() { initialValues: { username: '', email: '', password1: '', password2: '' } }); const navigate = useNavigate(); - const [auth_settings] = useServerApiState((state) => [state.auth_settings]); + const [auth_settings, registration_enabled, sso_registration] = + useServerApiState((state) => [ + state.auth_settings, + state.registration_enabled, + state.sso_registration_enabled + ]); const [isRegistering, setIsRegistering] = useState<boolean>(false); function handleRegistration() { @@ -232,11 +242,10 @@ export function RegistrationForm() { }); } - const both_reg_enabled = - auth_settings?.registration_enabled && auth_settings?.sso_registration; + const both_reg_enabled = registration_enabled() && sso_registration(); return ( <> - {auth_settings?.registration_enabled && ( + {registration_enabled() && ( <form onSubmit={registrationForm.onSubmit(() => {})}> <Stack gap={0}> <TextInput @@ -285,9 +294,9 @@ export function RegistrationForm() { {both_reg_enabled && ( <Divider label={t`Or use SSO`} labelPosition='center' my='lg' /> )} - {auth_settings?.sso_registration === true && ( + {sso_registration() && ( <Group grow mb='md' mt='md'> - {auth_settings.providers.map((provider) => ( + {auth_settings?.socialaccount.providers.map((provider) => ( <SsoButton provider={provider} key={provider.id} /> ))} </Group> @@ -303,13 +312,13 @@ export function ModeSelector({ loginMode: boolean; setMode: any; }>) { - const [auth_settings] = useServerApiState((state) => [state.auth_settings]); - const registration_enabled = - auth_settings?.registration_enabled || - auth_settings?.sso_registration || - false; + const [sso_registration, registration_enabled] = useServerApiState( + (state) => [state.sso_registration_enabled, state.registration_enabled] + ); + const both_reg_enabled = + registration_enabled() || sso_registration() || false; - if (registration_enabled === false) return null; + if (both_reg_enabled === false) return null; return ( <Text ta='center' size={'xs'} mt={'md'}> {loginMode ? ( diff --git a/src/frontend/src/enums/ApiEndpoints.tsx b/src/frontend/src/enums/ApiEndpoints.tsx index 180b6f26d177..d9e60b4ec801 100644 --- a/src/frontend/src/enums/ApiEndpoints.tsx +++ b/src/frontend/src/enums/ApiEndpoints.tsx @@ -19,14 +19,14 @@ export enum ApiEndpoints { user_reset = 'auth/password/reset/', user_reset_set = 'auth/password/reset/confirm/', user_change_password = 'auth/password/change/', - user_sso = 'auth/social/', - user_sso_remove = 'auth/social/:id/disconnect/', + user_sso = '_allauth/app/v1/account/providers', user_login = '_allauth/app/v1/auth/login', user_login_mfa = '_allauth/app/v1/auth/2fa/authenticate', user_logout = '_allauth/app/v1/auth/session', user_register = 'auth/registration/', user_mfa = '_allauth/app/v1/account/authenticators', user_emails = '_allauth/app/v1/account/email', + login_provider_redirect = '_allauth/browser/v1/auth/provider/redirect', // Generic API endpoints currency_list = 'currency/exchange/', @@ -44,14 +44,13 @@ export enum ApiEndpoints { custom_state_list = 'generic/status/custom/', version = 'version/', license = 'license/', - sso_providers = 'auth/providers/', group_list = 'user/group/', owner_list = 'user/owner/', content_type_list = 'contenttype/', icons = 'icons/', selectionlist_list = 'selection/', selectionlist_detail = 'selection/:id/', - securtiy_settings = '_allauth/app/v1/config', + securtiy_settings = '_allauth/browser/v1/config', // Barcode API endpoints barcode = 'barcode/', @@ -221,6 +220,5 @@ export enum ApiEndpoints { error_report_list = 'error-report/', project_code_list = 'project-code/', custom_unit_list = 'units/', - ui_preference = 'web/ui_preference/', notes_image_upload = 'notes-image-upload/' } diff --git a/src/frontend/src/functions/auth.tsx b/src/frontend/src/functions/auth.tsx index b09526f2d4b0..dc75d90d8431 100644 --- a/src/frontend/src/functions/auth.tsx +++ b/src/frontend/src/functions/auth.tsx @@ -7,7 +7,7 @@ import { ApiEndpoints } from '../enums/ApiEndpoints'; import { apiUrl } from '../states/ApiState'; import { useLocalState } from '../states/LocalState'; import { useUserState } from '../states/UserState'; -import { fetchGlobalStates } from '../states/states'; +import { type Provider, fetchGlobalStates } from '../states/states'; import { showLoginNotification } from './notifications'; export function followRedirect(navigate: NavigateFunction, redirect: any) { @@ -293,3 +293,20 @@ export function clearCsrfCookie() { document.cookie = 'csrftoken=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/;'; } + +export function ProviderLogin( + provider: Provider, + process: 'login' | 'connect' = 'login' +) { + const { host } = useLocalState.getState(); + // TODO + const loc = window.location; + const values = { + provider: provider.id, + callback_url: 'http://localhost:8000/logged-in', + process: process, + csrfmiddlewaretoken: getCsrfCookie() + }; + const url = `${host}${apiUrl(ApiEndpoints.login_provider_redirect)}`; + post(url, values); +} diff --git a/src/frontend/src/pages/Index/Settings/AccountSettings/SecurityContent.tsx b/src/frontend/src/pages/Index/Settings/AccountSettings/SecurityContent.tsx index ce7aa1b53301..703f323631fe 100644 --- a/src/frontend/src/pages/Index/Settings/AccountSettings/SecurityContent.tsx +++ b/src/frontend/src/pages/Index/Settings/AccountSettings/SecurityContent.tsx @@ -11,8 +11,7 @@ import { Table, Text, TextInput, - Title, - Tooltip + Title } from '@mantine/core'; import { IconAlertCircle, IconAt } from '@tabler/icons-react'; import { useQuery } from '@tanstack/react-query'; @@ -22,28 +21,15 @@ import { api, queryClient } from '../../../../App'; import { YesNoButton } from '../../../../components/buttons/YesNoButton'; import { PlaceholderPill } from '../../../../components/items/Placeholder'; import { ApiEndpoints } from '../../../../enums/ApiEndpoints'; -import { apiUrl } from '../../../../states/ApiState'; +import { ProviderLogin } from '../../../../functions/auth'; +import { apiUrl, useServerApiState } from '../../../../states/ApiState'; import { useUserState } from '../../../../states/UserState'; +import type { Provider, SecuritySetting } from '../../../../states/states'; export function SecurityContent() { - const [isSsoEnabled, setIsSsoEnabled] = useState<boolean>(false); - const [isMfaEnabled, setIsMfaEnabled] = useState<boolean>(false); - - const { isLoading: isLoadingProvider, data: dataProvider } = useQuery({ - queryKey: ['sso-providers'], - queryFn: () => - api.get(apiUrl(ApiEndpoints.sso_providers)).then((res) => res.data) - }); - - // evaluate if security options are enabled - useEffect(() => { - if (dataProvider === undefined) return; - - // check if SSO is enabled on the server - setIsSsoEnabled(dataProvider.sso_enabled || false); - // check if MFa is enabled - setIsMfaEnabled(dataProvider.mfa_required || false); - }, [dataProvider]); + const [auth_settings, sso_enabled, mfa_enabled] = useServerApiState( + (state) => [state.auth_settings, state.sso_enabled, state.mfa_enabled] + ); return ( <Stack> @@ -54,8 +40,8 @@ export function SecurityContent() { <Title order={5}> <Trans>Single Sign On Accounts</Trans> - {isSsoEnabled ? ( - + {sso_enabled() ? ( + ) : ( } @@ -69,7 +55,7 @@ export function SecurityContent() { Multifactor - {isMfaEnabled ? ( + {mfa_enabled() ? ( ) : ( ) { +function ProviderButton({ provider }: Readonly<{ provider: Provider }>) { + return ( + + ); +} + +function SsoContent({ + auth_settings +}: Readonly<{ auth_settings: SecuritySetting | undefined }>) { const [value, setValue] = useState(''); - const [currentProviders, setCurrentProviders] = useState<[]>(); + const [currentProviders, setCurrentProviders] = useState(); + const { session } = useUserState.getState(); const { isLoading, data } = useQuery({ queryKey: ['sso-list'], queryFn: () => - api.get(apiUrl(ApiEndpoints.user_sso)).then((res) => res.data) + api + .get(apiUrl(ApiEndpoints.user_sso), { + headers: { 'X-Session-Token': session } + }) + .then((res) => res.data.data) }); useEffect(() => { - if (dataProvider === undefined) return; + if (auth_settings === undefined) return; if (data === undefined) return; const configuredProviders = data.map((item: any) => { @@ -270,14 +275,16 @@ function SsoContent({ dataProvider }: Readonly<{ dataProvider: any }>) { } // remove providers that are used currently - let newData = dataProvider.providers; - newData = newData.filter(isAlreadyInUse); + const newData = + auth_settings.socialaccount.providers.filter(isAlreadyInUse); setCurrentProviders(newData); - }, [dataProvider, data]); + }, [auth_settings, data]); function removeProvider() { api - .post(apiUrl(ApiEndpoints.user_sso_remove, undefined, { id: value })) + .delete(apiUrl(ApiEndpoints.user_sso), { + headers: { 'X-Session-Token': session } + }) .then(() => { queryClient.removeQueries({ queryKey: ['sso-list'] @@ -289,28 +296,6 @@ function SsoContent({ dataProvider }: Readonly<{ dataProvider: any }>) { /* renderer */ if (isLoading) return ; - function ProviderButton({ provider }: Readonly<{ provider: any }>) { - const button = ( - - ); - - if (provider.configured) return button; - return ( - {button} - ); - } - return ( diff --git a/src/frontend/src/states/ApiState.tsx b/src/frontend/src/states/ApiState.tsx index 82989dd1373a..52b93ae7a64e 100644 --- a/src/frontend/src/states/ApiState.tsx +++ b/src/frontend/src/states/ApiState.tsx @@ -11,11 +11,16 @@ interface ServerApiStateProps { setServer: (newServer: ServerAPIProps) => void; fetchServerApiState: () => void; auth_settings?: SecuritySetting; + sso_enabled: () => boolean; + mfa_enabled: () => boolean; + registration_enabled: () => boolean; + sso_registration_enabled: () => boolean; + password_forgotten_enabled: () => boolean; } export const useServerApiState = create()( persist( - (set) => ({ + (set, get) => ({ server: emptyServerAPI, setServer: (newServer: ServerAPIProps) => set({ server: newServer }), fetchServerApiState: async () => { @@ -24,6 +29,7 @@ export const useServerApiState = create()( .get(apiUrl(ApiEndpoints.api_server_info)) .then((response) => { set({ server: response.data }); + // set sso_enabled }) .catch(() => { console.error('ERR: Error fetching server info'); @@ -41,7 +47,27 @@ export const useServerApiState = create()( console.error('ERR: Error fetching SSO information'); }); }, - status: undefined + auth_settings: undefined, + sso_enabled: () => { + const data = get().auth_settings?.socialaccount.providers; + return !(data === undefined || data.length == 0); + }, + mfa_enabled: () => { + // TODO + return true; + }, + registration_enabled: () => { + // TODO + return false; + }, + sso_registration_enabled: () => { + // TODO + return false; + }, + password_forgotten_enabled: () => { + // TODO + return false; + } }), { name: 'server-api-state', diff --git a/src/frontend/src/states/states.tsx b/src/frontend/src/states/states.tsx index 47f0df3de5c5..c6b1fb26f1be 100644 --- a/src/frontend/src/states/states.tsx +++ b/src/frontend/src/states/states.tsx @@ -50,15 +50,6 @@ export interface ServerAPIProps { django_admin: null | string; } -export interface AuthProps { - sso_enabled: boolean; - sso_registration: boolean; - mfa_required: boolean; - providers: Provider[]; - registration_enabled: boolean; - password_forgotten_enabled: boolean; -} - export interface SecuritySetting { account: { authentication_method: string; @@ -79,15 +70,6 @@ export interface Provider { client_id: string; } -export interface Provider { - id: string; - name: string; - configured: boolean; - login: string; - connect: string; - display_name: string; -} - // Type interface defining a single 'setting' object export interface Setting { pk: number; From e19c2e1c62f9891dd65d924a5b560b768296a03a Mon Sep 17 00:00:00 2001 From: Matthias Mair Date: Tue, 7 Jan 2025 23:34:45 +0100 Subject: [PATCH 057/156] simplify calls --- src/frontend/src/functions/auth.tsx | 69 +++++++++----- .../AccountSettings/SecurityContent.tsx | 95 ++++--------------- 2 files changed, 66 insertions(+), 98 deletions(-) diff --git a/src/frontend/src/functions/auth.tsx b/src/frontend/src/functions/auth.tsx index dc75d90d8431..8fd807e6a592 100644 --- a/src/frontend/src/functions/auth.tsx +++ b/src/frontend/src/functions/auth.tsx @@ -1,6 +1,7 @@ import { t } from '@lingui/macro'; import { notifications } from '@mantine/notifications'; import axios from 'axios'; +import type { AxiosRequestConfig } from 'axios'; import type { Location, NavigateFunction } from 'react-router-dom'; import { api, setApiDefaults } from '../App'; import { ApiEndpoints } from '../enums/ApiEndpoints'; @@ -127,16 +128,12 @@ export const doBasicLogin = async ( */ export const doLogout = async (navigate: NavigateFunction) => { const { clearUserState, isLoggedIn, setSession } = useUserState.getState(); - const { session } = useUserState.getState(); // Logout from the server session if (isLoggedIn() || !!getCsrfCookie()) { - await api - .delete(apiUrl(ApiEndpoints.user_logout), { - headers: { 'X-Session-Token': session } - }) - .catch(() => {}); - + await authApi(apiUrl(ApiEndpoints.user_logout), undefined, 'delete').catch( + () => {} + ); showLoginNotification({ title: t`Logged Out`, message: t`Successfully logged out` @@ -201,21 +198,14 @@ export function handleMfaLogin( location: Location, values: { code: string } ) { - const { session, setToken, setSession } = useUserState.getState(); - - api - .post( - apiUrl(ApiEndpoints.user_login_mfa), - { - code: values.code - }, - { headers: { 'X-Session-Token': session } } - ) - .then((response) => { - setSession(response.data.meta.session_token); - setToken(response.data.meta.access_token); - followRedirect(navigate, location?.state); - }); + const { setToken, setSession } = useUserState.getState(); + authApi(apiUrl(ApiEndpoints.user_login_mfa), undefined, 'post', { + code: values.code + }).then((response) => { + setSession(response.data.meta.session_token); + setToken(response.data.meta.access_token); + followRedirect(navigate, location?.state); + }); } /** @@ -310,3 +300,38 @@ export function ProviderLogin( const url = `${host}${apiUrl(ApiEndpoints.login_provider_redirect)}`; post(url, values); } + +/** + * Makes an API request with session tokens using the provided URL, configuration, method, and data. + * + * @param url - The URL to which the request is sent. + * @param config - Optional Axios request configuration. + * @param method - The HTTP method to use for the request. Defaults to 'get'. + * @param data - Optional data to be sent with the request. + * @returns A promise that resolves to the response of the API request. + */ +export function authApi( + url: string, + config: AxiosRequestConfig | undefined = undefined, + method: 'get' | 'post' | 'put' | 'delete' = 'get', + data?: any +) { + const [session] = useUserState((state) => [state.session]); + // extend default axios instance with session token + const requestConfig = config || {}; + if (!requestConfig.headers) { + requestConfig.headers = {}; + } + requestConfig.headers['X-Session-Token'] = session; + + // set method + requestConfig.method = method; + + // set data + if (data) { + requestConfig.data = data; + } + + // use normal api + return api.post(url, requestConfig); +} diff --git a/src/frontend/src/pages/Index/Settings/AccountSettings/SecurityContent.tsx b/src/frontend/src/pages/Index/Settings/AccountSettings/SecurityContent.tsx index 703f323631fe..2bd2ff0bb5de 100644 --- a/src/frontend/src/pages/Index/Settings/AccountSettings/SecurityContent.tsx +++ b/src/frontend/src/pages/Index/Settings/AccountSettings/SecurityContent.tsx @@ -21,9 +21,8 @@ import { api, queryClient } from '../../../../App'; import { YesNoButton } from '../../../../components/buttons/YesNoButton'; import { PlaceholderPill } from '../../../../components/items/Placeholder'; import { ApiEndpoints } from '../../../../enums/ApiEndpoints'; -import { ProviderLogin } from '../../../../functions/auth'; +import { ProviderLogin, authApi } from '../../../../functions/auth'; import { apiUrl, useServerApiState } from '../../../../states/ApiState'; -import { useUserState } from '../../../../states/UserState'; import type { Provider, SecuritySetting } from '../../../../states/states'; export function SecurityContent() { @@ -80,75 +79,25 @@ export function SecurityContent() { function EmailContent() { const [value, setValue] = useState(''); const [newEmailValue, setNewEmailValue] = useState(''); - const [session] = useUserState((state) => [state.session]); const { isLoading, data, refetch } = useQuery({ queryKey: ['emails'], queryFn: () => - api - .get(apiUrl(ApiEndpoints.user_emails), { - headers: { 'X-Session-Token': session } - }) - .then((res) => res.data.data) + authApi(apiUrl(ApiEndpoints.user_emails)).then((res) => res.data.data) }); function runServerAction( - url: ApiEndpoints, - action: 'post' | 'put' | 'delete' = 'post' + action: 'post' | 'put' | 'delete' = 'post', + data?: any ) { - let act: any; - switch (action) { - case 'post': - act = api.post; - break; - case 'put': - act = api.put; - break; - case 'delete': - act = api.delete; - break; - } - act( - apiUrl(url), - { email: value }, - { headers: { 'X-Session-Token': session } } - ) + const vals: any = data || { email: value }; + console.log('vals', vals); + authApi(apiUrl(ApiEndpoints.user_emails), undefined, action, vals) .then(() => { refetch(); }) .catch((res: any) => console.log(res.data)); } - function addEmail() { - api - .post( - apiUrl(ApiEndpoints.user_emails), - { - email: newEmailValue - }, - { headers: { 'X-Session-Token': session } } - ) - .then(() => { - refetch(); - }) - .catch((res) => console.log(res.data)); - } - - function changePrimary() { - api - .post( - apiUrl(ApiEndpoints.user_emails), - { - email: value, - primary: true - }, - { headers: { 'X-Session-Token': session } } - ) - .then(() => { - refetch(); - }) - .catch((res) => console.log(res.data)); - } - if (isLoading) return ; return ( @@ -211,23 +160,25 @@ function EmailContent() { - + - - @@ -252,15 +203,10 @@ function SsoContent({ }: Readonly<{ auth_settings: SecuritySetting | undefined }>) { const [value, setValue] = useState(''); const [currentProviders, setCurrentProviders] = useState(); - const { session } = useUserState.getState(); const { isLoading, data } = useQuery({ queryKey: ['sso-list'], queryFn: () => - api - .get(apiUrl(ApiEndpoints.user_sso), { - headers: { 'X-Session-Token': session } - }) - .then((res) => res.data.data) + authApi(apiUrl(ApiEndpoints.user_sso)).then((res) => res.data.data) }); useEffect(() => { @@ -281,10 +227,7 @@ function SsoContent({ }, [auth_settings, data]); function removeProvider() { - api - .delete(apiUrl(ApiEndpoints.user_sso), { - headers: { 'X-Session-Token': session } - }) + authApi(apiUrl(ApiEndpoints.user_sso), undefined, 'delete') .then(() => { queryClient.removeQueries({ queryKey: ['sso-list'] From 18fdfc9b22e29e9bbc852ed628727ad13844b273 Mon Sep 17 00:00:00 2001 From: Matthias Mair Date: Wed, 8 Jan 2025 00:10:28 +0100 Subject: [PATCH 058/156] make calls more robust --- src/frontend/src/functions/auth.tsx | 6 +++--- src/frontend/src/views/MainView.tsx | 8 ++++++-- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/src/frontend/src/functions/auth.tsx b/src/frontend/src/functions/auth.tsx index 8fd807e6a592..35a6baadeea3 100644 --- a/src/frontend/src/functions/auth.tsx +++ b/src/frontend/src/functions/auth.tsx @@ -316,13 +316,13 @@ export function authApi( method: 'get' | 'post' | 'put' | 'delete' = 'get', data?: any ) { - const [session] = useUserState((state) => [state.session]); + const state = useUserState.getState(); // extend default axios instance with session token const requestConfig = config || {}; if (!requestConfig.headers) { requestConfig.headers = {}; } - requestConfig.headers['X-Session-Token'] = session; + requestConfig.headers['X-Session-Token'] = state.session; // set method requestConfig.method = method; @@ -333,5 +333,5 @@ export function authApi( } // use normal api - return api.post(url, requestConfig); + return api(url, requestConfig); } diff --git a/src/frontend/src/views/MainView.tsx b/src/frontend/src/views/MainView.tsx index 56c2bb77e601..c874ae728848 100644 --- a/src/frontend/src/views/MainView.tsx +++ b/src/frontend/src/views/MainView.tsx @@ -20,8 +20,12 @@ export default function MainView() { const [allowMobile] = useLocalState((state) => [state.allowMobile]); // Set initial login status useEffect(() => { - // Local state initialization - setApiDefaults(); + try { + // Local state initialization + setApiDefaults(); + } catch (e) { + console.error(e); + } }, []); // Check if mobile From d482b3677f9654e3edf77ea5261c2c52e6b169b9 Mon Sep 17 00:00:00 2001 From: Matthias Mair Date: Wed, 8 Jan 2025 13:55:20 +0100 Subject: [PATCH 059/156] switch to browser based sessions --- src/backend/InvenTree/users/api.py | 3 ++- src/frontend/src/enums/ApiEndpoints.tsx | 12 ++++++------ src/frontend/src/functions/auth.tsx | 9 ++++++++- 3 files changed, 16 insertions(+), 8 deletions(-) diff --git a/src/backend/InvenTree/users/api.py b/src/backend/InvenTree/users/api.py index edaa46aeaba1..ffdd4431808a 100644 --- a/src/backend/InvenTree/users/api.py +++ b/src/backend/InvenTree/users/api.py @@ -5,6 +5,7 @@ from django.contrib.auth import get_user, login from django.contrib.auth.models import Group, User from django.urls import include, path, re_path +from django.views.decorators.csrf import ensure_csrf_cookie from django.views.generic.base import RedirectView import structlog @@ -330,7 +331,7 @@ def get_redirect_url(self, *args, **kwargs): user_urls = [ path('roles/', RoleDetails.as_view(), name='api-user-roles'), - path('token/', GetAuthToken.as_view(), name='api-token'), + path('token/', ensure_csrf_cookie(GetAuthToken.as_view()), name='api-token'), path( 'tokens/', include([ diff --git a/src/frontend/src/enums/ApiEndpoints.tsx b/src/frontend/src/enums/ApiEndpoints.tsx index d9e60b4ec801..360dfc07f918 100644 --- a/src/frontend/src/enums/ApiEndpoints.tsx +++ b/src/frontend/src/enums/ApiEndpoints.tsx @@ -19,13 +19,13 @@ export enum ApiEndpoints { user_reset = 'auth/password/reset/', user_reset_set = 'auth/password/reset/confirm/', user_change_password = 'auth/password/change/', - user_sso = '_allauth/app/v1/account/providers', - user_login = '_allauth/app/v1/auth/login', - user_login_mfa = '_allauth/app/v1/auth/2fa/authenticate', - user_logout = '_allauth/app/v1/auth/session', + user_sso = '_allauth/browser/v1/account/providers', + user_login = '_allauth/browser/v1/auth/login', + user_login_mfa = '_allauth/browser/v1/auth/2fa/authenticate', + user_logout = '_allauth/browser/v1/auth/session', user_register = 'auth/registration/', - user_mfa = '_allauth/app/v1/account/authenticators', - user_emails = '_allauth/app/v1/account/email', + user_mfa = '_allauth/browser/v1/account/authenticators', + user_emails = '_allauth/browser/v1/account/email', login_provider_redirect = '_allauth/browser/v1/auth/provider/redirect', // Generic API endpoints diff --git a/src/frontend/src/functions/auth.tsx b/src/frontend/src/functions/auth.tsx index 35a6baadeea3..c55f50587b9c 100644 --- a/src/frontend/src/functions/auth.tsx +++ b/src/frontend/src/functions/auth.tsx @@ -73,9 +73,16 @@ export const doBasicLogin = async ( } clearCsrfCookie(); + const cookie = getCsrfCookie(); const login_url = apiUrl(ApiEndpoints.user_login); + if (cookie == undefined) { + await api.get(apiUrl(ApiEndpoints.user_token)).catch(() => { + // his is to be expected + }); + } + let loginDone = false; let success = false; @@ -115,7 +122,7 @@ export const doBasicLogin = async ( if (loginDone) { await fetchUserState(); fetchGlobalStates(); - } else { + } else if (!success) { clearUserState(); } return success; From 2eea51a9f188a24e8b7e0789964a47141a549043 Mon Sep 17 00:00:00 2001 From: Matthias Mair Date: Wed, 8 Jan 2025 19:55:19 +0100 Subject: [PATCH 060/156] add todo's --- src/frontend/src/enums/ApiEndpoints.tsx | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/frontend/src/enums/ApiEndpoints.tsx b/src/frontend/src/enums/ApiEndpoints.tsx index 360dfc07f918..32c66b2b3abc 100644 --- a/src/frontend/src/enums/ApiEndpoints.tsx +++ b/src/frontend/src/enums/ApiEndpoints.tsx @@ -16,14 +16,14 @@ export enum ApiEndpoints { user_token = 'user/token/', user_tokens = 'user/tokens/', user_simple_login = 'email/generate/', - user_reset = 'auth/password/reset/', - user_reset_set = 'auth/password/reset/confirm/', - user_change_password = 'auth/password/change/', + user_reset = 'auth/password/reset/', // TODO change + user_reset_set = 'auth/password/reset/confirm/', // TODO change + user_change_password = 'auth/password/change/', // TODO change user_sso = '_allauth/browser/v1/account/providers', user_login = '_allauth/browser/v1/auth/login', user_login_mfa = '_allauth/browser/v1/auth/2fa/authenticate', user_logout = '_allauth/browser/v1/auth/session', - user_register = 'auth/registration/', + user_register = 'auth/registration/', // TODO change user_mfa = '_allauth/browser/v1/account/authenticators', user_emails = '_allauth/browser/v1/account/email', login_provider_redirect = '_allauth/browser/v1/auth/provider/redirect', From 7ef84c87a2c49b1eb130d627b7b0d03fa5c1f548 Mon Sep 17 00:00:00 2001 From: Matthias Mair Date: Wed, 8 Jan 2025 19:56:33 +0100 Subject: [PATCH 061/156] update api version --- src/backend/InvenTree/InvenTree/api_version.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/backend/InvenTree/InvenTree/api_version.py b/src/backend/InvenTree/InvenTree/api_version.py index 36b12a9223c8..4c726d62be6b 100644 --- a/src/backend/InvenTree/InvenTree/api_version.py +++ b/src/backend/InvenTree/InvenTree/api_version.py @@ -1,13 +1,13 @@ """InvenTree API version information.""" # InvenTree API version -INVENTREE_API_VERSION = 298 +INVENTREE_API_VERSION = 299 """Increment this API version number whenever there is a significant change to the API that any clients need to know about.""" INVENTREE_API_TEXT = """ -v296 - 2024-12-22 : https://github.com/inventree/InvenTree/pull/6293 +v299 - 2025-01-10 : https://github.com/inventree/InvenTree/pull/6293 - Removes a considerable amount of old auth endpoints - Introduces allauth based REST API From a00f090a87f3aa2195216b9ebf1f00d9c6046976 Mon Sep 17 00:00:00 2001 From: Matthias Mair Date: Wed, 8 Jan 2025 20:03:42 +0100 Subject: [PATCH 062/156] remove x-session, not needed anymore --- src/backend/InvenTree/InvenTree/settings.py | 3 --- src/frontend/src/functions/auth.tsx | 17 +++-------------- src/frontend/src/states/UserState.tsx | 6 ------ 3 files changed, 3 insertions(+), 23 deletions(-) diff --git a/src/backend/InvenTree/InvenTree/settings.py b/src/backend/InvenTree/InvenTree/settings.py index dd99b6411aff..dee0b7e64403 100644 --- a/src/backend/InvenTree/InvenTree/settings.py +++ b/src/backend/InvenTree/InvenTree/settings.py @@ -20,7 +20,6 @@ from django.http import Http404, HttpResponseGone import structlog -from corsheaders.defaults import default_headers from dotenv import load_dotenv from zoneinfo import ZoneInfo, ZoneInfoNotFoundError @@ -1164,8 +1163,6 @@ # Ref: https://github.com/adamchainz/django-cors-headers -CORS_ALLOW_HEADERS = (*default_headers, 'x-session-token') - # Extract CORS options from configuration file CORS_ALLOW_ALL_ORIGINS = get_boolean_setting( 'INVENTREE_CORS_ORIGIN_ALLOW_ALL', config_key='cors.allow_all', default_value=DEBUG diff --git a/src/frontend/src/functions/auth.tsx b/src/frontend/src/functions/auth.tsx index c55f50587b9c..09bdf49f60b6 100644 --- a/src/frontend/src/functions/auth.tsx +++ b/src/frontend/src/functions/auth.tsx @@ -65,8 +65,7 @@ export const doBasicLogin = async ( navigate: NavigateFunction ) => { const { host } = useLocalState.getState(); - const { clearUserState, setToken, setSession, fetchUserState } = - useUserState.getState(); + const { clearUserState, setToken, fetchUserState } = useUserState.getState(); if (username.length == 0 || password.length == 0) { return; @@ -100,7 +99,6 @@ export const doBasicLogin = async ( ) .then((response) => { if (response.status == 200 && response.data?.meta?.is_authenticated) { - setSession(response.data.meta.session_token); setToken(response.data.meta.access_token); loginDone = true; success = true; @@ -112,7 +110,6 @@ export const doBasicLogin = async ( (flow: any) => flow.id == 'mfa_authenticate' ); if (mfa_flow && mfa_flow.is_pending == true) { - setSession(err.response.data.meta.session_token); success = true; navigate('/mfa'); } @@ -134,7 +131,7 @@ export const doBasicLogin = async ( * @arg deleteToken: If true, delete the token from the server */ export const doLogout = async (navigate: NavigateFunction) => { - const { clearUserState, isLoggedIn, setSession } = useUserState.getState(); + const { clearUserState, isLoggedIn } = useUserState.getState(); // Logout from the server session if (isLoggedIn() || !!getCsrfCookie()) { @@ -147,7 +144,6 @@ export const doLogout = async (navigate: NavigateFunction) => { }); } - setSession(undefined); clearUserState(); clearCsrfCookie(); navigate('/login'); @@ -205,11 +201,10 @@ export function handleMfaLogin( location: Location, values: { code: string } ) { - const { setToken, setSession } = useUserState.getState(); + const { setToken } = useUserState.getState(); authApi(apiUrl(ApiEndpoints.user_login_mfa), undefined, 'post', { code: values.code }).then((response) => { - setSession(response.data.meta.session_token); setToken(response.data.meta.access_token); followRedirect(navigate, location?.state); }); @@ -323,13 +318,7 @@ export function authApi( method: 'get' | 'post' | 'put' | 'delete' = 'get', data?: any ) { - const state = useUserState.getState(); - // extend default axios instance with session token const requestConfig = config || {}; - if (!requestConfig.headers) { - requestConfig.headers = {}; - } - requestConfig.headers['X-Session-Token'] = state.session; // set method requestConfig.method = method; diff --git a/src/frontend/src/states/UserState.tsx b/src/frontend/src/states/UserState.tsx index 965de1f6f5f0..8ba1abc88b07 100644 --- a/src/frontend/src/states/UserState.tsx +++ b/src/frontend/src/states/UserState.tsx @@ -16,8 +16,6 @@ export interface UserStateProps { setUser: (newUser: UserProps) => void; setToken: (newToken: string) => void; clearToken: () => void; - session: string | undefined; - setSession: (newSession: string | undefined) => void; fetchUserToken: () => void; fetchUserState: () => void; clearUserState: () => void; @@ -53,10 +51,6 @@ export const useUserState = create((set, get) => ({ set({ token: undefined }); setApiDefaults(); }, - session: undefined, - setSession: (newSession: string | undefined) => { - set({ session: newSession }); - }, userId: () => { const user: UserProps = get().user as UserProps; return user.pk; From 1191d1cd82bb90380b18c3e05835235fe41617b2 Mon Sep 17 00:00:00 2001 From: Matthias Mair Date: Wed, 8 Jan 2025 20:04:55 +0100 Subject: [PATCH 063/156] remove old urls --- src/backend/InvenTree/InvenTree/urls.py | 20 -------------------- 1 file changed, 20 deletions(-) diff --git a/src/backend/InvenTree/InvenTree/urls.py b/src/backend/InvenTree/InvenTree/urls.py index 5dabdf34d568..33403031f36c 100644 --- a/src/backend/InvenTree/InvenTree/urls.py +++ b/src/backend/InvenTree/InvenTree/urls.py @@ -95,26 +95,6 @@ path( 'auth/', include([ - # TODO remove - # re_path( - # r'^registration/account-confirm-email/(?P[-:\w]+)/$', - # ConfirmEmailView.as_view(), - # name='account_confirm_email', - # ), - # path('registration/', include('dj_rest_auth.registration.urls')), - # path( - # 'providers/', SocialProviderListView.as_view(), name='social_providers' - # ), - # path('social/', include(get_provider_urls())), - # path( - # 'social/', SocialAccountListView.as_view(), name='social_account_list' - # ), - # path( - # 'social//disconnect/', - # SocialAccountDisconnectView.as_view(), - # name='social_account_disconnect', - # ), - # path('login/', users.api.Login.as_view(), name='api-login'), path('logout/', users.api.Logout.as_view(), name='api-logout'), path( 'login-redirect/', From f84ce83e8f0f1670115e48623df91ac55c379480 Mon Sep 17 00:00:00 2001 From: Matthias Mair Date: Wed, 8 Jan 2025 20:07:28 +0100 Subject: [PATCH 064/156] remove ui preference - there is no decision anymore --- src/backend/InvenTree/InvenTree/urls.py | 2 - src/backend/InvenTree/users/test_api.py | 5 --- src/backend/InvenTree/web/tests.py | 27 +---------- src/backend/InvenTree/web/urls.py | 59 ------------------------- 4 files changed, 1 insertion(+), 92 deletions(-) diff --git a/src/backend/InvenTree/InvenTree/urls.py b/src/backend/InvenTree/InvenTree/urls.py index 33403031f36c..958fcf317d7f 100644 --- a/src/backend/InvenTree/InvenTree/urls.py +++ b/src/backend/InvenTree/InvenTree/urls.py @@ -25,7 +25,6 @@ import stock.api import users.api from plugin.urls import get_plugin_urls -from web.urls import api_urls as web_api_urls from web.urls import urlpatterns as platform_urls from .api import ( @@ -73,7 +72,6 @@ ]), ), path('user/', include(users.api.user_urls)), - path('web/', include(web_api_urls)), # Plugin endpoints path('', include(plugin.api.plugin_api_urls)), # Common endpoints endpoint diff --git a/src/backend/InvenTree/users/test_api.py b/src/backend/InvenTree/users/test_api.py index eac45537a8a8..d812aa96cd09 100644 --- a/src/backend/InvenTree/users/test_api.py +++ b/src/backend/InvenTree/users/test_api.py @@ -96,11 +96,6 @@ def test_group_api(self): def test_login_redirect(self): """Test login redirect endpoint.""" response = self.get(reverse('api-login-redirect'), expected_code=302) - self.assertEqual(response.url, '/index/') - - # PUI - self.put(reverse('api-ui-preference'), {'preferred_method': 'pui'}) - response = self.get(reverse('api-login-redirect'), expected_code=302) self.assertEqual(response.url, '/platform/logged-in/') diff --git a/src/backend/InvenTree/web/tests.py b/src/backend/InvenTree/web/tests.py index 4603f25ded0d..72d5f5d402fd 100644 --- a/src/backend/InvenTree/web/tests.py +++ b/src/backend/InvenTree/web/tests.py @@ -5,10 +5,8 @@ from pathlib import Path from unittest import mock -from django.urls import reverse - from InvenTree.config import get_frontend_settings -from InvenTree.unit_test import InvenTreeAPITestCase, InvenTreeTestCase +from InvenTree.unit_test import InvenTreeTestCase from .templatetags import spa_helper @@ -92,26 +90,3 @@ def test_redirects(self): """Test the redirect helper.""" response = self.client.get('/assets/testpath') self.assertEqual(response.url, '/static/web/assets/testpath') - - -class TestWebHelpers(InvenTreeAPITestCase): - """Tests for the web helpers.""" - - def test_ui_preference(self): - """Test the UI preference API.""" - url = reverse('api-ui-preference') - - # Test default - resp = self.get(url) - data = json.loads(resp.content) - self.assertTrue(data['cui']) - self.assertFalse(data['pui']) - self.assertEqual(data['preferred_method'], 'cui') - - # Set to PUI - resp = self.put(url, {'preferred_method': 'pui'}) - data = json.loads(resp.content) - self.assertEqual(resp.status_code, 200) - self.assertFalse(data['cui']) - self.assertTrue(data['pui']) - self.assertEqual(data['preferred_method'], 'pui') diff --git a/src/backend/InvenTree/web/urls.py b/src/backend/InvenTree/web/urls.py index 6199abe26415..208dd7d51205 100644 --- a/src/backend/InvenTree/web/urls.py +++ b/src/backend/InvenTree/web/urls.py @@ -1,16 +1,11 @@ """URLs for web app.""" from django.conf import settings -from django.http import JsonResponse from django.shortcuts import redirect from django.urls import include, path, re_path from django.views.decorators.csrf import ensure_csrf_cookie from django.views.generic import TemplateView -from rest_framework import permissions, serializers - -from InvenTree.mixins import RetrieveUpdateAPI - class RedirectAssetView(TemplateView): """View to redirect to static asset.""" @@ -22,55 +17,6 @@ def get(self, request, *args, **kwargs): ) -class PreferredSerializer(serializers.Serializer): - """Serializer for the preferred serializer session setting.""" - - preferred_method = serializers.ChoiceField(choices=['cui', 'pui']) - pui = serializers.SerializerMethodField(read_only=True) - cui = serializers.SerializerMethodField(read_only=True) - - def get_pui(self, obj) -> bool: - """Return true if preferred method is PUI.""" - return obj['preferred_method'] == 'pui' - - def get_cui(self, obj) -> bool: - """Return true if preferred method is CUI.""" - return obj['preferred_method'] == 'cui' - - class Meta: - """Meta class for PreferedSerializer.""" - - fields = '__all__' - - -class PreferredUiView(RetrieveUpdateAPI): - """Set preferred UI (CIU/PUI).""" - - permission_classes = [permissions.AllowAny] - serializer_class = PreferredSerializer - http_method_names = ['get', 'post', 'put', 'head', 'options'] - - def retrieve(self, request, *args, **kwargs): - """Retrieve the preferred UI method.""" - session = self.request.session - session['preferred_method'] = session.get('preferred_method', 'cui') - serializer = self.get_serializer(data=dict(session)) - serializer.is_valid(raise_exception=True) - return JsonResponse(serializer.data) - - def update(self, request, *args, **kwargs): - """Update the preferred UI method.""" - serializer = self.get_serializer(data=self.clean_data(request.data)) - serializer.is_valid(raise_exception=True) - - # Run update - session = self.request.session - session['preferred_method'] = serializer.validated_data['preferred_method'] - session.modified = True - - return JsonResponse(serializer.data) - - spa_view = ensure_csrf_cookie(TemplateView.as_view(template_name='web/index.html')) assets_path = path('assets/', RedirectAssetView.as_view()) @@ -91,8 +37,3 @@ def update(self, request, *args, **kwargs): assets_path, path(settings.FRONTEND_URL_BASE, spa_view, name='platform'), ] - -api_urls = [ - # UI Preference - path('ui_preference/', PreferredUiView.as_view(), name='api-ui-preference') -] From cb8779a08290ab70cff35e187c41c28afa737836 Mon Sep 17 00:00:00 2001 From: Matthias Mair Date: Wed, 8 Jan 2025 20:41:53 +0100 Subject: [PATCH 065/156] fix login redirect logic --- src/backend/InvenTree/users/api.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/backend/InvenTree/users/api.py b/src/backend/InvenTree/users/api.py index ffdd4431808a..96ae6095ae09 100644 --- a/src/backend/InvenTree/users/api.py +++ b/src/backend/InvenTree/users/api.py @@ -323,10 +323,7 @@ class LoginRedirect(RedirectView): def get_redirect_url(self, *args, **kwargs): """Return the URL to redirect to.""" - session = self.request.session - if session.get('preferred_method', 'cui') == 'pui': - return f'/{FRONTEND_URL_BASE}/logged-in/' - return '/index/' + return f'/{FRONTEND_URL_BASE}/logged-in/' user_urls = [ From 29694d27c84a8287c87c29084d37c8ae0af62dcc Mon Sep 17 00:00:00 2001 From: Matthias Mair Date: Wed, 8 Jan 2025 20:50:37 +0100 Subject: [PATCH 066/156] change name to ensure 1p can detect field --- src/frontend/src/pages/Auth/MFALogin.tsx | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/frontend/src/pages/Auth/MFALogin.tsx b/src/frontend/src/pages/Auth/MFALogin.tsx index 5be5a2d99453..f4b1d2adbdd8 100644 --- a/src/frontend/src/pages/Auth/MFALogin.tsx +++ b/src/frontend/src/pages/Auth/MFALogin.tsx @@ -29,8 +29,9 @@ export default function Reset() { From 4b6ab95b4c8f54efebe5f439c3df44b83810d3ca Mon Sep 17 00:00:00 2001 From: Matthias Mair Date: Wed, 8 Jan 2025 21:03:31 +0100 Subject: [PATCH 067/156] add mfa table --- .../AccountSettings/SecurityContent.tsx | 52 +++++++++++++++++-- 1 file changed, 47 insertions(+), 5 deletions(-) diff --git a/src/frontend/src/pages/Index/Settings/AccountSettings/SecurityContent.tsx b/src/frontend/src/pages/Index/Settings/AccountSettings/SecurityContent.tsx index 2bd2ff0bb5de..8806bce8521f 100644 --- a/src/frontend/src/pages/Index/Settings/AccountSettings/SecurityContent.tsx +++ b/src/frontend/src/pages/Index/Settings/AccountSettings/SecurityContent.tsx @@ -19,7 +19,6 @@ import { useEffect, useMemo, useState } from 'react'; import { api, queryClient } from '../../../../App'; import { YesNoButton } from '../../../../components/buttons/YesNoButton'; -import { PlaceholderPill } from '../../../../components/items/Placeholder'; import { ApiEndpoints } from '../../../../enums/ApiEndpoints'; import { ProviderLogin, authApi } from '../../../../functions/auth'; import { apiUrl, useServerApiState } from '../../../../states/ApiState'; @@ -297,11 +296,54 @@ function SsoContent({ } function MfaContent() { + const { isLoading, data, refetch } = useQuery({ + queryKey: ['mfa-list'], + queryFn: () => + api.get(apiUrl(ApiEndpoints.user_mfa)).then((res) => res.data.data) + }); + + function parseDate(date: number) { + if (date == null) return 'Never'; + return new Date(date * 1000).toLocaleString(); + } + const rows = useMemo(() => { + if (isLoading || data === undefined) return null; + return data.map((token: any) => ( + + {token.type} + {parseDate(token.last_used_at)} + {parseDate(token.created_at)} + + )); + }, [data, isLoading]); + + /* renderer */ + if (isLoading) return ; + + if (data.length == 0) + return ( + } color='green'> + No factors configured + + ); + return ( - <> - MFA Details - - + + + + + Type + + + Last used at + + + Created at + + + + {rows} +
); } From da918b7f184cb6665f5fd4d64a1da65caa236d32 Mon Sep 17 00:00:00 2001 From: Matthias Mair Date: Wed, 8 Jan 2025 21:58:25 +0100 Subject: [PATCH 068/156] fix remove sso provider account action; provider (user) admin stuff is done --- .../AccountSettings/SecurityContent.tsx | 26 +++++++++++-------- 1 file changed, 15 insertions(+), 11 deletions(-) diff --git a/src/frontend/src/pages/Index/Settings/AccountSettings/SecurityContent.tsx b/src/frontend/src/pages/Index/Settings/AccountSettings/SecurityContent.tsx index 8806bce8521f..35250150f662 100644 --- a/src/frontend/src/pages/Index/Settings/AccountSettings/SecurityContent.tsx +++ b/src/frontend/src/pages/Index/Settings/AccountSettings/SecurityContent.tsx @@ -17,7 +17,7 @@ import { IconAlertCircle, IconAt } from '@tabler/icons-react'; import { useQuery } from '@tanstack/react-query'; import { useEffect, useMemo, useState } from 'react'; -import { api, queryClient } from '../../../../App'; +import { api } from '../../../../App'; import { YesNoButton } from '../../../../components/buttons/YesNoButton'; import { ApiEndpoints } from '../../../../enums/ApiEndpoints'; import { ProviderLogin, authApi } from '../../../../functions/auth'; @@ -202,7 +202,7 @@ function SsoContent({ }: Readonly<{ auth_settings: SecuritySetting | undefined }>) { const [value, setValue] = useState(''); const [currentProviders, setCurrentProviders] = useState(); - const { isLoading, data } = useQuery({ + const { isLoading, data, refetch } = useQuery({ queryKey: ['sso-list'], queryFn: () => authApi(apiUrl(ApiEndpoints.user_sso)).then((res) => res.data.data) @@ -213,7 +213,7 @@ function SsoContent({ if (data === undefined) return; const configuredProviders = data.map((item: any) => { - return item.provider; + return item.provider.id; }); function isAlreadyInUse(value: any) { return !configuredProviders.includes(value.id); @@ -226,11 +226,15 @@ function SsoContent({ }, [auth_settings, data]); function removeProvider() { - authApi(apiUrl(ApiEndpoints.user_sso), undefined, 'delete') + const split = value.split('$'); + const provider = split[split.length - 1]; + const uid = split.slice(0, split.length - 1).join('$'); + authApi(apiUrl(ApiEndpoints.user_sso), undefined, 'delete', { + provider: provider, + account: uid + }) .then(() => { - queryClient.removeQueries({ - queryKey: ['sso-list'] - }); + refetch(); }) .catch((res) => console.log(res.data)); } @@ -262,15 +266,15 @@ function SsoContent({ {data.map((link: any) => ( ))}
)} From 6624dbf6c0b874ff3bee87d9ba7087043efe6bdd Mon Sep 17 00:00:00 2001 From: Matthias Mair Date: Wed, 8 Jan 2025 22:25:52 +0100 Subject: [PATCH 069/156] reduce templates to the raw basics --- .../InvenTree/static/css/inventree.css | 1114 ----------------- src/backend/InvenTree/templates/403.html | 11 +- src/backend/InvenTree/templates/403_csrf.html | 12 +- src/backend/InvenTree/templates/404.html | 11 +- src/backend/InvenTree/templates/500.html | 13 +- src/backend/InvenTree/templates/503.html | 60 +- .../InvenTree/templates/account/base.html | 37 - src/backend/InvenTree/templates/skeleton.html | 6 +- 8 files changed, 13 insertions(+), 1251 deletions(-) delete mode 100644 src/backend/InvenTree/InvenTree/static/css/inventree.css delete mode 100644 src/backend/InvenTree/templates/account/base.html diff --git a/src/backend/InvenTree/InvenTree/static/css/inventree.css b/src/backend/InvenTree/InvenTree/static/css/inventree.css deleted file mode 100644 index cc392d28b727..000000000000 --- a/src/backend/InvenTree/InvenTree/static/css/inventree.css +++ /dev/null @@ -1,1114 +0,0 @@ -:root { - --primary-color: #335d88; - --secondary-color: #eef3f7; - --highlight-color: #ffffff; - - --border-color: #ccc; - - --label-red: #e35a57; - --label-blue: #4194bd; - --label-green: #50aa51; - --label-grey: #aaa; - --label-yellow: #fdc82a; - - --bs-body-color: #68686a; - - --scanner-upscale: 5; - --scanner-margin: 10%; -} - -main { - overflow-x: auto; -} - -.login-screen { - background-size: cover; - background-position: center; - height: 100vh; - font-family: 'Numans', sans-serif; - color: #eee; -} - -.login-container { - align-self: center; - border-radius: 15px; - padding: 20px; - padding-bottom: 35px; - background-color: rgba(50, 50, 50, 0.75); - width: 100%; - max-width: 550px; - margin: auto; -} - -.login-header { - margin-right: 5px; -} - -.login-container input { - background-color: rgba(250, 250, 250, 0.9); -} - -.login-button { - background-color: rgba(250, 250, 250, 0.9); - color: #333; - border-color: #AAA; - width: 100%; - border-radius: 5px; -} - -.index-bg { - width: 100%; - object-fit: fill; - opacity: 5%; -} - -.index-action-selected { - background-color: #EEEEF5; -} - -.panel-content { - padding: 10px; -} - -/* Progress bars */ - -.progress { - position: relative; - width: 100%; - margin-bottom: 0px; - background: #eeeef5; - font-size: 75%; - height: 1.25rem; - border-radius: 10px; -} - -.progress-bar { - opacity: 60%; - background: #2aa02a; -} - -.progress-bar-under { - background: #eeaa33; -} - -.progress-bar-over { - background: #337ab7; -} - -.progress-value { - width: 100%; - color: #333; - position: absolute; - text-align: center; - top: 0px; - left: 0px; - font-size: 110%; -} - -.bg-qr-code { - background-color: #FFF !important; -} - -.qr-code { - max-width: 400px; - max-height: 400px; - align-content: center; -} - -.navbar { - border-bottom: 1px solid #ccc; - background-color: var(--secondary-color); - box-shadow: 0px 5px 5px rgb(0 0 0 / 5%); -} - -.inventree-navbar-menu { - position: absolute !important; -} - -.navbar-brand { - float: left; -} - -.navbar-spacer { - height: 60px; -} - -#navbar-barcode-li { - border-left: none; - border-right: none; - padding-right: 5px; -} - -.navbar-form { - padding-right: 3px; -} - -.navbar-light .navbar-nav .nav-link { - color: var(--bs-body-color); -} - -#barcode-scan { - margin-top: 1px; -} - -.icon-header { - margin-right: 10px; -} - -.glyphicon { - font-size: 18px; -} - - -.glyphicon-small { - font-size: 12px; -} - -.glyphicon-right { - float: right; -} - -.red-cell { - background-color: #ec7f7f; -} - -.part-price { - color: rgb(13, 245, 25); -} - -.icon-red { - color: #c55; -} - -.icon-orange { - color: #fcba03; -} - -.icon-green { - color: #43bb43; -} - -.icon-blue { - color: #55c; -} - -.icon-yellow { - color: #CC2; -} - -/* CSS overrides for treeview */ -.expand-icon { - font-size: 11px; -} - -.treeview .badge { - font-size: 10px; -} - -.treeview .list-group-item { - padding: 3px 5px; -} - -.treeview .list-group-item .indent { - margin-left: 3px; - margin-right: 3px; -} - -.list-group-item-condensed { - padding: 5px 10px; -} - -.stock-sub-group td { - background-color: #ebf4f4; -} - -.sub-table { - margin-left: 60px; -} - -.detail-icon .glyphicon { - color: #98d296; -} - -.basecurrency { - color: #050; - font-style: italic; - font-weight: bold; -} - -.bomselect { - max-width: 250px; -} - -.rowvalid { - color: #050; -} - -.rowinvalid { - color: #A00; -} - -.rowinherited { - background-color: #eee; - font-style: italic; -} - -.dropdown { - padding-left: 1px; - margin-left: 1px; -} - -.dropdown-buttons { - display: inline-block -} - -.dropdown-menu .open{ - z-index: 1000; - position: relative; - overflow: visible; -} - -/* Styles for table buttons and filtering */ - -.filter-list { - display: inline-block; - *display: inline; - margin-bottom: 1px; - margin-top: 1px; - vertical-align: middle; - margin: 1px; - padding: 2px; - border-radius: 3px; -} - -.filter-list .close { - cursor: pointer; - right: 0%; - padding-right: 2px; - padding-left: 2px; - transform: translate(0%, -25%); -} - -.filter-list .close:hover { - background: #bbb; -} - -.filter-list .form-control { - width: initial; -} - -.filter-tag { - display: inline-block; - *display: inline; - margin: 5px; - padding: 5px; - padding-top: 1px; - padding-bottom: 1px; - color: var(--bs-body-color); - border: 1px solid var(--border-color); - border-radius: 10px; - background: var(--secondary-color); - white-space: nowrap; -} - -.filter-button { - padding: 6px; -} - -.filter-input { - display: inline-block; - *display: inline; -} - -.filter-tag:hover { - background: #ddd; -} - -/* Part image icons with full-display on mouse hover */ - -.hover-img-thumb { - background: #eee; - width: 28px; - height: 28px; - object-fit: contain; - border: 1px solid #cce; -} - -.hover-img-large { - background: #eee; - display: none; - position: absolute; - z-index: 400; - border: 1px solid #555; - max-width: 250px; -} - -.hover-icon { - margin-right: 10px; -} - -.hover-icon:hover > .hover-img-large { - display: block; -} - -/* dropzone class - for Drag-n-Drop file uploads */ -.dropzone { - z-index: 2; -} - -/* -.dropzone * { - pointer-events: none; -} -*/ - -.dragover { - background-color: #55A; - border: 1px dashed #111; - opacity: 0.1; - -moz-opacity: 10%; - -webkit-opacity: 10%; -} - -.table-condensed { - font-size: 90%; -} - -.table-condensed td { - padding: .25em .5em; -} - -.table button { - font-size: 90%; - padding: 3px; - padding-left: 5px; - padding-right: 5px; -} - -/* grid display for part images */ - -.table-img-grid tr { - display: inline; -} - -.table-img-grid td { - padding: 10px; - margin: 10px; -} - -.table-img-grid .grid-image { - - height: 128px; - width: 128px; - object-fit: contain; - background: #eee; -} - -/* pricing table widths */ -.table-price-two tr td:first-child { - width: 40%; -} - -.table-price-three tr td:first-child { - width: 40%; -} - -.table-price-two tr td:last-child { - width: 60%; -} - -.table-price-three tr td:last-child { - width: 30%; -} - -/* tracking table column size */ -#track-table .table-condensed th { - inline-size: 30%; - overflow-wrap: break-word; -} - -.panel-heading .badge { - float: right; -} - -.badge-right { - float: right; -} - -.icon-badge { - padding-right: 2px; - padding-left: 2px; - font-size: 125%; -} - -.part-properties > span { - padding-left: 5px; - padding-right: 5px; -} - -.part-thumb { - width: 256px; - height: 256px; - margin: 2px; - padding: 3px; - object-fit: contain; -} - -.part-thumb-container:hover .part-thumb-overlay { - opacity: 0.75; -} - -.part-thumb-overlay { - position: absolute; - top: 0; - left: 0; - opacity: 0; - transition: .25s ease; - margin: 5px; -} - -.checkbox { - margin-left: 20px; -} - -.checkboxinput { - padding-left: 5px; - padding-right: 5px; -} - -.form-switch { - font-size: 120%; -} - -.media { - /* padding-top: 15px; */ - overflow: visible; -} - -.media-body { - padding-top: 10px; - overflow: visible; -} - -.navigation { - background-color: var(--secondary-color); -} - -.nav-tabs { - margin-bottom: 20px; -} - -.settings-container { - width: 90%; - padding: 15px; -} - -.settings-nav { - height: 100%; - width: 160px; - position: fixed; - z-index: 1; - /* top: 0; - left: 0; */ - overflow-x: hidden; - padding-top: 20px; - padding-right: 25px; -} - -.settings-content { - margin-left: 175px; - padding: 0px 10px; -} - -.breadcrumb { - margin-bottom: 5px; - margin-left: 5px; - margin-right: 10px; -} - -.inventree-body { - width: 100%; - padding: 5px; - padding-right: 0; -} - -.inventree-pre-content { - width: auto; -} - -.inventree-content { - padding-left: 10px; - padding-right: 10px; - padding-top: 5px; - width: 100%; - max-width: 100%; - display: inline-block; - transition: 0.1s; -} - -.search-autocomplete-item { - border-top: 1px solid #EEE; - margin-bottom: 2px; - overflow-x: hidden; -} - -.modal { - overflow: hidden; - z-index: 9999; -} - -.modal-error { - border: 2px #FCC solid; - background-color: #f5f0f0; -} - -.modal-header { - padding: 3px; - padding-top: 5px; - padding-left: 15px; - padding-right: 25px; - color: var(--bs-body-color); - background-color: var(--secondary-color); - border-bottom: 1px solid var(--border-color); -} - -.modal-footer { - border-top: 1px solid #ddd; -} - -.modal-primary { - z-index: 10000; -} - -.modal-secondary { - z-index: 11000; -} - -.modal-close { - position: absolute; - top: 15px; - right: 35px; - color: #f1f1f1; - font-size: 40px; - font-weight: bold; - transition: 0.25s; -} - -.modal-close:hover, -.modal-close:focus { - color: #bbb; - text-decoration: none; - cursor: pointer; -} - -.modal-image-content { - margin: auto; - display: block; - width: 80%; - max-width: 700px; - text-align: center; - color: #ccc; - padding: 10px 0; -} - -@media only screen and (max-width: 700px){ - .modal-image-content { - width: 100%; - } -} - -.modal-image { - display: none; - position: fixed; - z-index: 10000; - padding-top: 100px; - left: 0; - top: 0; - width: 100%; - height: 100%; - overflow: auto; - background-color: rgb(0,0,0); /* Fallback color */ - background-color: rgba(0,0,0,0.85); /* Black w/ opacity */ -} - -.js-modal-form .checkbox { - margin-left: 0px; -} - -.modal-dialog { - width: 65%; - max-width: 80%; -} - -.modal-secondary .modal-dialog { - width: 40%; - padding-top: 15px; -} - -.modal-content h3 { - margin-top: 3px; - margin-bottom: 3px; -} - -.modal-form-content-wrapper { - border-radius: 0; - position:relative; - height: auto !important; - max-height: calc(100vh - 200px) !important; - overflow-y: auto; - padding: 10px; -} - -.form-panel { - border-radius: 5px; - border: 1px solid #ccc; - padding: 5px; -} - -.form-group { - padding-bottom: 15px; -} - - -.form-field-error { - color: #A94442; -} - -.form-error-message { - display: block; -} - -.modal input { - width: 100%; -} - -input[type="submit"] { - color: #333; - background-color: #e6e6e6; - border-color: #adadad; -} - -.modal textarea { - width: 100%; -} - -/* Force a control-label div to be 100% width */ -.modal .control-label { - width: 100%; - font-weight: 700; - margin-top: 5px; -} - -.modal .control-label .btn { - padding-top: 3px; - padding-bottom: 3px; -} - -.modal .btn-form-secondary { - margin-bottom: 10px; -} - -/* Make barcode scanner responsive and performant */ -#barcode_scan_video video { - width: calc(100% * var(--scanner-upscale)) !important; - transform: scale(calc(1 / var(--scanner-upscale))); - margin: calc(-100% * (var(--scanner-upscale) - 1) / 2); - display: inline-block !important; -} - -@-moz-document url-prefix() { - #barcode_scan_video video { - margin-top: calc(-100% * (var(--scanner-upscale) - 1) / 2 + 50%); - margin-bottom: calc(-100% * (var(--scanner-upscale) - 1) / 2 + 50%); - } - - @media (pointer:coarse) { - #barcode_scan_video video { - margin-top: calc(-100% * (var(--scanner-upscale)) / 2 - 20%); - margin-bottom: calc(-100% * (var(--scanner-upscale)) / 2 - 20%); - } - } -} - -#barcode_scan_video #qr-shaded-region { - border: none !important; - margin: var(--scanner-margin); -} - -.sidebar-list-group-item { - background-color: var(--secondary-color); - color: var(--bs-body-color); -} - -.sidebar-list-group-item.active { - color: var(--highlight-color); - background-color: var(--bs-body-color); - border: none; -} - -.container > aside, -.container > main { - padding: 10px; - overflow: auto; -} - - -.wrapper { - align-items: stretch; - display: flex; -} - -.help-inline { - color: #A11; -} - -.notification-area { - opacity: 0.8; -} - -.notes { - border-radius: 5px; - background-color: #fafafa; - padding: 5px; -} - -.alert { - border-radius: 5px; - opacity: 0.9; - pointer-events: all; -} - -.alert-block { - display: block; - padding: 0.75rem; -} - -.alert-small { - padding: 0.35rem; - font-size: 75%; -} - -.navbar .btn { - margin-left: 5px; -} - -.btn-secondary { - background-color: var(--bs-body-color); - border-color: var(--bs-body-color); -} - -.btn-outline-secondary { - color: var(--bs-body-color); - border-color: var(--bs-body-color); -} - -.btn-small { - padding: 3px; - padding-left: 5px; - padding-right: 5px; -} - -.btn-remove { - padding: 3px; - padding-left: 5px; - padding-right: 5px; - color: #A11; -} - -.btn-create { - padding: 3px; - padding-left: 5px; - padding-right: 5px; - color: #1A1; -} - -.btn-edit { - padding: 3px; - padding: 3px; - padding-left: 5px; - padding-right: 5px; - color: #55E; -} - -.button-toolbar { - padding-left: 0px; -} - -.panel-group { - margin-bottom: 5px; -} - -.panel-content { - padding: 10px; -} - -.panel-group { - border-radius: 2px; -} - -.panel-heading { - padding: 3px; - padding-top: 5px; - padding-left: 15px; - color: var(--bs-body-color); - background-color: var(--secondary-color); - border-bottom: 1px solid var(--border-color); - box-shadow: 0px 5px 5px rgb(0 0 0 / 5%); -} - -.panel { - box-shadow: 2px 2px #DDD; - margin-bottom: .75rem; - background-color: #fff; - border: 1px solid #ccc; -} - -.panel-hidden { - display: none; -} - -.panel-inventree .card { - padding: 10px; -} - -.card-thumb { - max-width: 64px; - max-height: 64px; -} - -.float-right { - float: right; -} - -.warning-msg { - color: #e00; -} - -.info-messages { - padding: 5px; -} - -.info-messages .alert { - padding: 5px; - margin-bottom: 10px; -} - -.part-allocation { - padding: 3px 10px; - border: 1px solid #ccc; - border-radius: 2px; -} - -.part-allocation-pass { - background-color: #dbf0db; -} - -.part-allocation-underallocated { - background-color: #f0dbdb; -} - -.part-allocation-overallocated { - background-color: #ccf5ff; -} - -.glyphicon-refresh-animate { - -animation: spin .7s infinite linear; - -webkit-animation: spin2 .7s infinite linear; -} - -@-webkit-keyframes spin2 { - from { -webkit-transform: rotate(0deg);} - to { -webkit-transform: rotate(360deg);} -} - -@keyframes spin { - from { transform: scale(1) rotate(0deg);} - to { transform: scale(1) rotate(360deg);} -} - -input[type="date"].form-control, input[type="time"].form-control, input[type="datetime-local"].form-control, input[type="month"].form-control { - line-height: unset; -} - -.clip-btn { - font-size: 10px; - padding: 0px 6px; - color: var(--label-grey); - background: none; -} - -.clip-btn:hover { - background: var(--label-grey); -} - -.sidebar-wrapper { - overflow-y: auto; - /* background: var(--secondary-color); */ - margin-top: 0.3rem; - padding-top: 0.25rem; - padding-left: 0px !important; -} - -.sidebar-item-icon { - min-width: 20px; -} - -.sidebar-item-text { - margin-left: 15px; - margin-right: 15px; -} - -.sidebar-nav a { - color: var(--bs-body-color); -} - -.row.full-height { - display: flex; - flex-wrap: wrap; -} - -.row.full-height > [class*='col-'] { - display: flex; - flex-direction: column; -} - -a.anchor { - display: block; - position: relative; - top: -60px; - visibility: hidden; -} - -.select2-close-mask { - z-index: 99999; -} - -.select2-dropdown { - z-index: 99998; -} - - -.select2-thumbnail { - max-width: 24px; - max-height: 24px; - border-radius: 4px; - margin-right: 10px; -} - -.select2-selection { - overflow-y: clip; -} - -.form-clear { - padding: 6px 6px; -} - -/* Force minimum width of number input fields to show at least ~5 digits */ -input[type='number']{ - min-width: 80px; -} - -.search-menu { - padding-top: 2rem; -} - -.search-menu .ui-menu-item { - margin-top: 0.5rem; -} - -.product-card { - width: 20%; - padding: 5px; - min-height: 25px; - background-color: transparent; -} - -.product-card-panel{ - height: 100%; - margin-bottom: 5px; -} - -.borderless { - border: none; -} - -a { - text-decoration: none; - background-color: transparent; -} - -/* Quicksearch Panel */ - -.search-result-panel { - max-width: 800px; - width: 75% -} - -.search-result-group { - padding: 5px; - padding-left: 10px; - padding-right: 10px; - border: 1px solid var(--border-color); - margin-bottom: 10px; -} - -.search-result-group-buttons > button{ - padding: 2px; - padding-left: 5px; - padding-right: 5px; - font-size: 80%; -} - -.search-result-entry { - border-top: 1px solid var(--border-color); - padding: 3px; - margin-top: 3px; - overflow: hidden; -} - -.treeview .node-icon { - margin-left: 0.25rem; - margin-right: 0.25rem; -} - -.sso-header { - text-align: center; - width: 100%; - padding-bottom: 10px; -} - -.sso-provider-list { - width: 100%; - list-style-type: none; - padding-left: 0px; -} - -.sso-provider-link { - width: 100%; -} - -.sso-provider-link a { - width: 100%; - text-align: left; -} - -.flex-cell { - display: flex; - align-items: center; - justify-content: space-between; -} - -.large-treeview-icon { - font-size: 1em; -} - -.api-icon { - font-style: normal; - font-weight: normal; - font-variant: normal; - text-transform: none; - line-height: 1; - /* Better font rendering */ - -webkit-font-smoothing: antialiased; - -moz-osx-font-smoothing: grayscale; -} diff --git a/src/backend/InvenTree/templates/403.html b/src/backend/InvenTree/templates/403.html index fb76ba597601..1f69ed473ae9 100644 --- a/src/backend/InvenTree/templates/403.html +++ b/src/backend/InvenTree/templates/403.html @@ -7,13 +7,6 @@ {% endblock page_title %} {% block content %} - -
-

{% trans "Permission Denied" %}

- -
- {% trans "You do not have permission to view this page." %} -
-
- +

{% trans "Permission Denied" %}

+{% trans "You do not have permission to view this page." %} {% endblock content %} diff --git a/src/backend/InvenTree/templates/403_csrf.html b/src/backend/InvenTree/templates/403_csrf.html index e66345fb56f0..701d3965dd25 100644 --- a/src/backend/InvenTree/templates/403_csrf.html +++ b/src/backend/InvenTree/templates/403_csrf.html @@ -9,15 +9,5 @@ {% block content %}

{% trans "Authentication Failure" %}

- -
- {% trans "You have been logged out from InvenTree." %} -
-
- - +{% trans "You have been logged out from InvenTree." %} {% endblock content %} diff --git a/src/backend/InvenTree/templates/404.html b/src/backend/InvenTree/templates/404.html index 15531651bcae..021d4ebbc738 100644 --- a/src/backend/InvenTree/templates/404.html +++ b/src/backend/InvenTree/templates/404.html @@ -7,13 +7,6 @@ {% endblock page_title %} {% block content %} - -
-

{% trans "Page Not Found" %}

- -
- {% trans "The requested page does not exist" %} -
-
- +

{% trans "Page Not Found" %}

+{% trans "The requested page does not exist" %} {% endblock content %} diff --git a/src/backend/InvenTree/templates/500.html b/src/backend/InvenTree/templates/500.html index 33a0ad2c6b58..eb395c0859a2 100644 --- a/src/backend/InvenTree/templates/500.html +++ b/src/backend/InvenTree/templates/500.html @@ -7,14 +7,7 @@ {% endblock page_title %} {% block content %} - -
-

{% trans "Internal Server Error" %}

- -
- {% blocktrans %}The {{ inventree_title }} server raised an internal error{% endblocktrans %}
- {% trans "Refer to the error log in the admin interface for further details" %} -
-
- +

{% trans "Internal Server Error" %}

+{% blocktrans %}The {{ inventree_title }} server raised an internal error{% endblocktrans %}
+{% trans "Refer to the error log in the admin interface for further details" %} {% endblock content %} diff --git a/src/backend/InvenTree/templates/503.html b/src/backend/InvenTree/templates/503.html index 8630d794a020..f0671796ed7e 100644 --- a/src/backend/InvenTree/templates/503.html +++ b/src/backend/InvenTree/templates/503.html @@ -11,61 +11,9 @@ {% trans 'Site is in Maintenance' %} {% endblock page_title %} -{% block body_class %}login-screen' style='background: url({% inventree_splash %}); background-size: cover;{% endblock body_class %} - {% block body %} - -
-
- -
-
- - +

{% block body_title %}{% trans 'Site is in Maintenance' %}{% endblock body_title %}

+{% block content %} +{% trans 'The site is currently in maintenance and should be up again soon!' %} +{% endblock content %} {% endblock body %} - -{% block js_base %} - -{% endblock js_base %} - - diff --git a/src/backend/InvenTree/templates/account/base.html b/src/backend/InvenTree/templates/account/base.html deleted file mode 100644 index 0e298e8cc0bc..000000000000 --- a/src/backend/InvenTree/templates/account/base.html +++ /dev/null @@ -1,37 +0,0 @@ -{% extends "skeleton.html" %} -{% load i18n %} -{% load inventree_extras %} - -{% block body %} - -
-
- -
-
- - -{% endblock body %} diff --git a/src/backend/InvenTree/templates/skeleton.html b/src/backend/InvenTree/templates/skeleton.html index 3e77e83f9fb3..4c06cc31db63 100644 --- a/src/backend/InvenTree/templates/skeleton.html +++ b/src/backend/InvenTree/templates/skeleton.html @@ -12,18 +12,14 @@ {% include "favicon.html" %} - - - {% block page_title %} {% endblock page_title %} - + {% block body %} {% endblock body %} - From f0dc4a030e739918037757b0f04930e681485760 Mon Sep 17 00:00:00 2001 From: Matthias Mair Date: Wed, 8 Jan 2025 22:44:56 +0100 Subject: [PATCH 070/156] fix tests --- src/frontend/tests/baseFixtures.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/frontend/tests/baseFixtures.ts b/src/frontend/tests/baseFixtures.ts index 6c70e3e38d89..cf9778ceae74 100644 --- a/src/frontend/tests/baseFixtures.ts +++ b/src/frontend/tests/baseFixtures.ts @@ -71,6 +71,8 @@ export const test = baseTest.extend({ url != 'http://localhost:8000/this/does/not/exist.js' && url != 'http://localhost:8000/api/user/me/' && url != 'http://localhost:8000/api/user/token/' && + url != 'http://localhost:8000/api/_allauth/browser/v1/auth/login' && + url != 'http://localhost:8000/api/auth/v1/auth/login' && url != 'http://localhost:8000/api/barcode/' && url != 'https://docs.inventree.org/en/versions.json' && url != 'http://localhost:5173/favicon.ico' && From b5223dedf100722303a6d93f78a419d01ef3fdaf Mon Sep 17 00:00:00 2001 From: Matthias Mair Date: Wed, 8 Jan 2025 22:46:04 +0100 Subject: [PATCH 071/156] more exclusions --- src/frontend/tests/baseFixtures.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/frontend/tests/baseFixtures.ts b/src/frontend/tests/baseFixtures.ts index cf9778ceae74..733c1d97814b 100644 --- a/src/frontend/tests/baseFixtures.ts +++ b/src/frontend/tests/baseFixtures.ts @@ -73,6 +73,8 @@ export const test = baseTest.extend({ url != 'http://localhost:8000/api/user/token/' && url != 'http://localhost:8000/api/_allauth/browser/v1/auth/login' && url != 'http://localhost:8000/api/auth/v1/auth/login' && + url != 'http://localhost:8000/api/_allauth/browser/v1/auth/session' && + url != 'http://localhost:8000/api/auth/v1/auth/session' && url != 'http://localhost:8000/api/barcode/' && url != 'https://docs.inventree.org/en/versions.json' && url != 'http://localhost:5173/favicon.ico' && From 6fad126e21843a886e46d2f8dbb51d0da8bed67f Mon Sep 17 00:00:00 2001 From: Matthias Mair Date: Wed, 8 Jan 2025 22:58:28 +0100 Subject: [PATCH 072/156] rewrite url structure --- src/backend/InvenTree/InvenTree/urls.py | 7 +++++++ src/frontend/src/enums/ApiEndpoints.tsx | 16 ++++++++-------- src/frontend/tests/baseFixtures.ts | 2 -- 3 files changed, 15 insertions(+), 10 deletions(-) diff --git a/src/backend/InvenTree/InvenTree/urls.py b/src/backend/InvenTree/InvenTree/urls.py index 958fcf317d7f..5bd6d00992ae 100644 --- a/src/backend/InvenTree/InvenTree/urls.py +++ b/src/backend/InvenTree/InvenTree/urls.py @@ -10,6 +10,7 @@ from django.views.decorators.csrf import csrf_exempt from django.views.generic.base import RedirectView +from allauth.headless.urls import Client, build_urlpatterns from drf_spectacular.views import SpectacularAPIView, SpectacularRedocView from sesame.views import LoginView @@ -99,6 +100,12 @@ users.api.LoginRedirect.as_view(), name='api-login-redirect', ), + path( + '', + include( + (build_urlpatterns(Client.BROWSER), 'headless'), namespace='browser' + ), + ), ]), ), path('_allauth/', include('allauth.headless.urls')), diff --git a/src/frontend/src/enums/ApiEndpoints.tsx b/src/frontend/src/enums/ApiEndpoints.tsx index 32c66b2b3abc..c25e8d28db48 100644 --- a/src/frontend/src/enums/ApiEndpoints.tsx +++ b/src/frontend/src/enums/ApiEndpoints.tsx @@ -19,14 +19,14 @@ export enum ApiEndpoints { user_reset = 'auth/password/reset/', // TODO change user_reset_set = 'auth/password/reset/confirm/', // TODO change user_change_password = 'auth/password/change/', // TODO change - user_sso = '_allauth/browser/v1/account/providers', - user_login = '_allauth/browser/v1/auth/login', - user_login_mfa = '_allauth/browser/v1/auth/2fa/authenticate', - user_logout = '_allauth/browser/v1/auth/session', + user_sso = 'auth/v1/account/providers', + user_login = 'auth/v1/auth/login', + user_login_mfa = 'auth/v1/auth/2fa/authenticate', + user_logout = 'auth/v1/auth/session', user_register = 'auth/registration/', // TODO change - user_mfa = '_allauth/browser/v1/account/authenticators', - user_emails = '_allauth/browser/v1/account/email', - login_provider_redirect = '_allauth/browser/v1/auth/provider/redirect', + user_mfa = 'auth/v1/account/authenticators', + user_emails = 'auth/v1/account/email', + login_provider_redirect = 'auth/v1/auth/provider/redirect', // Generic API endpoints currency_list = 'currency/exchange/', @@ -50,7 +50,7 @@ export enum ApiEndpoints { icons = 'icons/', selectionlist_list = 'selection/', selectionlist_detail = 'selection/:id/', - securtiy_settings = '_allauth/browser/v1/config', + securtiy_settings = 'auth/v1/config', // Barcode API endpoints barcode = 'barcode/', diff --git a/src/frontend/tests/baseFixtures.ts b/src/frontend/tests/baseFixtures.ts index 733c1d97814b..8d3f3feded4a 100644 --- a/src/frontend/tests/baseFixtures.ts +++ b/src/frontend/tests/baseFixtures.ts @@ -71,9 +71,7 @@ export const test = baseTest.extend({ url != 'http://localhost:8000/this/does/not/exist.js' && url != 'http://localhost:8000/api/user/me/' && url != 'http://localhost:8000/api/user/token/' && - url != 'http://localhost:8000/api/_allauth/browser/v1/auth/login' && url != 'http://localhost:8000/api/auth/v1/auth/login' && - url != 'http://localhost:8000/api/_allauth/browser/v1/auth/session' && url != 'http://localhost:8000/api/auth/v1/auth/session' && url != 'http://localhost:8000/api/barcode/' && url != 'https://docs.inventree.org/en/versions.json' && From af4f0a48ed0888f346ea637a46f5059d03538162 Mon Sep 17 00:00:00 2001 From: Matthias Mair Date: Wed, 8 Jan 2025 23:09:38 +0100 Subject: [PATCH 073/156] move buildin token test --- src/backend/InvenTree/InvenTree/test_auth.py | 19 +++++++++++++++++++ src/backend/InvenTree/users/test_api.py | 12 ------------ 2 files changed, 19 insertions(+), 12 deletions(-) diff --git a/src/backend/InvenTree/InvenTree/test_auth.py b/src/backend/InvenTree/InvenTree/test_auth.py index cd6e00dd4725..091121be84ad 100644 --- a/src/backend/InvenTree/InvenTree/test_auth.py +++ b/src/backend/InvenTree/InvenTree/test_auth.py @@ -143,6 +143,25 @@ def __exit__(self, type, value, traceback): class TestAuth(InvenTreeAPITestCase): """Test authentication functionality.""" + def test_buildin_token(self): + """Test the built-in token authentication.""" + self.logout() + response = self.post( + '/api/auth/v1/auth/login', + {'username': self.username, 'password': self.password}, + expected_code=200, + ) + data = response.json() + self.assertIn('meta', data) + self.assertTrue(data['meta']['is_authenticated']) + + # Test for conflicting login + self.post( + '/api/auth/v1/auth/login', + {'username': self.username, 'password': self.password}, + expected_code=409, + ) + def email_args(self, user=None, email=None): """Generate registration arguments.""" return { diff --git a/src/backend/InvenTree/users/test_api.py b/src/backend/InvenTree/users/test_api.py index d812aa96cd09..b57b97e9655b 100644 --- a/src/backend/InvenTree/users/test_api.py +++ b/src/backend/InvenTree/users/test_api.py @@ -1,7 +1,6 @@ """API tests for various user / auth API endpoints.""" import datetime -import unittest from django.contrib.auth.models import Group, User from django.urls import reverse @@ -207,17 +206,6 @@ def test_token_auth(self): self.client.get(me, expected_code=200) - @unittest.skip - def test_buildin_token(self): - """Test the built-in token authentication.""" - response = self.post( - reverse('rest_login'), - {'username': self.username, 'password': self.password}, - expected_code=200, - ) - self.assertIn('key', response.data) - self.assertTrue(response.data['key'].startswith('inv-')) - def test_token_api(self): """Test the token API.""" url = reverse('api-token-list') From 31e25eb50b7d22139c4d310170dd8ada9fab5d72 Mon Sep 17 00:00:00 2001 From: Matthias Mair Date: Thu, 9 Jan 2025 00:10:17 +0100 Subject: [PATCH 074/156] re-enable registration tests --- src/backend/InvenTree/InvenTree/test_auth.py | 43 +++++++------------- 1 file changed, 15 insertions(+), 28 deletions(-) diff --git a/src/backend/InvenTree/InvenTree/test_auth.py b/src/backend/InvenTree/InvenTree/test_auth.py index 091121be84ad..82cfbda809d6 100644 --- a/src/backend/InvenTree/InvenTree/test_auth.py +++ b/src/backend/InvenTree/InvenTree/test_auth.py @@ -1,7 +1,5 @@ """Test the sso and auth module functionality.""" -import unittest - from django.conf import settings from django.contrib.auth.models import Group, User from django.core.exceptions import ValidationError @@ -143,6 +141,9 @@ def __exit__(self, type, value, traceback): class TestAuth(InvenTreeAPITestCase): """Test authentication functionality.""" + reg_url = '/api/auth/v1/auth/signup' + test_email = 'tester@example.com' + def test_buildin_token(self): """Test the built-in token authentication.""" self.logout() @@ -165,41 +166,29 @@ def test_buildin_token(self): def email_args(self, user=None, email=None): """Generate registration arguments.""" return { - 'username': user or 'user1', - 'email': email or 'test@example.com', - 'password1': '#asdf1234', - 'password2': '#asdf1234', + 'username': user or 'user2', + 'email': email or self.test_email, + 'password': '#asdf1234', } - @unittest.skip def test_registration(self): """Test the registration process.""" self.logout() # Duplicate username resp = self.post( - '/api/auth/registration/', - self.email_args(user='testuser'), - expected_code=400, - ) - self.assertIn( - 'A user with that username already exists.', resp.data['username'] + self.reg_url, self.email_args(user='testuser'), expected_code=400 ) + self.assertIn('A user with that username already exists.', str(resp.json())) # Registration is disabled - resp = self.post( - '/api/auth/registration/', self.email_args(), expected_code=400 - ) - self.assertIn('Registration is disabled.', resp.data['non_field_errors']) + self.post(self.reg_url, self.email_args(), expected_code=403) # Enable registration - now it should work with EmailSettingsContext(): - resp = self.post( - '/api/auth/registration/', self.email_args(), expected_code=201 - ) - self.assertIn('key', resp.data) + resp = self.post(self.reg_url, self.email_args(), expected_code=200) + self.assertEqual(resp.json()['data']['user']['email'], self.test_email) - @unittest.skip def test_registration_email(self): """Test that LOGIN_SIGNUP_MAIL_RESTRICTION works.""" self.logout() @@ -220,15 +209,13 @@ def test_registration_email(self): # Wrong email format resp = self.post( - '/api/auth/registration/', + self.reg_url, self.email_args(email='admin@invenhost.com'), expected_code=400, ) - self.assertIn('The provided email domain is not approved.', resp.data['email']) + self.assertIn('The provided email domain is not approved.', str(resp.json())) # Right format should work with EmailSettingsContext(): - resp = self.post( - '/api/auth/registration/', self.email_args(), expected_code=201 - ) - self.assertIn('key', resp.data) + resp = self.post(self.reg_url, self.email_args(), expected_code=200) + self.assertEqual(resp.json()['data']['user']['email'], self.test_email) From 8ad07c49d576cb30c46f3faa0bb0a83ffd2aebea Mon Sep 17 00:00:00 2001 From: Matthias Mair Date: Thu, 9 Jan 2025 00:52:14 +0100 Subject: [PATCH 075/156] re-implement registrations --- .../components/forms/AuthenticationForm.tsx | 47 ++++++++++++++----- src/frontend/src/enums/ApiEndpoints.tsx | 2 +- src/frontend/src/functions/auth.tsx | 15 +++--- 3 files changed, 45 insertions(+), 19 deletions(-) diff --git a/src/frontend/src/components/forms/AuthenticationForm.tsx b/src/frontend/src/components/forms/AuthenticationForm.tsx index 0335b76943be..e8e432c50f38 100644 --- a/src/frontend/src/components/forms/AuthenticationForm.tsx +++ b/src/frontend/src/components/forms/AuthenticationForm.tsx @@ -21,6 +21,7 @@ import { ApiEndpoints } from '../../enums/ApiEndpoints'; import { doBasicLogin, doSimpleLogin, + ensureCsrf, followRedirect } from '../../functions/auth'; import { showLoginNotification } from '../../functions/notifications'; @@ -196,7 +197,12 @@ export function AuthenticationForm() { export function RegistrationForm() { const registrationForm = useForm({ - initialValues: { username: '', email: '', password1: '', password2: '' } + initialValues: { + username: '', + email: '', + password: '', + password2: '' as string | undefined + } }); const navigate = useNavigate(); const [auth_settings, registration_enabled, sso_registration] = @@ -207,14 +213,26 @@ export function RegistrationForm() { ]); const [isRegistering, setIsRegistering] = useState(false); - function handleRegistration() { + async function handleRegistration() { + // check if passwords match + if ( + registrationForm.values.password !== registrationForm.values.password2 + ) { + registrationForm.setFieldError('password2', t`Passwords do not match`); + return; + } setIsRegistering(true); + + // remove password2 from the request + const { password2, ...vals } = registrationForm.values; + await ensureCsrf(); + api - .post(apiUrl(ApiEndpoints.user_register), registrationForm.values, { + .post(apiUrl(ApiEndpoints.user_register), vals, { headers: { Authorization: '' } }) .then((ret) => { - if (ret?.status === 204 || ret?.status === 201) { + if (ret?.status === 200) { setIsRegistering(false); showLoginNotification({ title: t`Registration successful`, @@ -226,16 +244,23 @@ export function RegistrationForm() { .catch((err) => { if (err.response?.status === 400) { setIsRegistering(false); - for (const [key, value] of Object.entries(err.response.data)) { - registrationForm.setFieldError(key, value as string); + + // collect all errors per field + const errors: { [key: string]: string[] } = {}; + for (const val of err.response.data.errors) { + if (!errors[val.param]) { + errors[val.param] = []; + } + errors[val.param].push(val.message); } - let err_msg = ''; - if (err.response?.data?.non_field_errors) { - err_msg = err.response.data.non_field_errors; + + for (const key in errors) { + registrationForm.setFieldError(key, errors[key]); } + showLoginNotification({ title: t`Input error`, - message: t`Check your input and try again. ` + err_msg, + message: t`Check your input and try again. `, success: false }); } @@ -268,7 +293,7 @@ export function RegistrationForm() { label={t`Password`} aria-label='register-password' placeholder={t`Your password`} - {...registrationForm.getInputProps('password1')} + {...registrationForm.getInputProps('password')} /> { - // his is to be expected - }); - } - let loginDone = false; let success = false; @@ -169,6 +163,13 @@ export const doSimpleLogin = async (email: string) => { return mail; }; +export async function ensureCsrf() { + const cookie = getCsrfCookie(); + if (cookie == undefined) { + await api.get(apiUrl(ApiEndpoints.user_token)).catch(() => {}); + } +} + export function handleReset( navigate: NavigateFunction, values: { email: string } From 1a876a7f6bc610bee432684e1ec173edfcf09004 Mon Sep 17 00:00:00 2001 From: Matthias Mair Date: Thu, 9 Jan 2025 00:52:33 +0100 Subject: [PATCH 076/156] enable registration for now --- src/frontend/src/states/ApiState.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/frontend/src/states/ApiState.tsx b/src/frontend/src/states/ApiState.tsx index 52b93ae7a64e..d231220ca57f 100644 --- a/src/frontend/src/states/ApiState.tsx +++ b/src/frontend/src/states/ApiState.tsx @@ -58,7 +58,7 @@ export const useServerApiState = create()( }, registration_enabled: () => { // TODO - return false; + return true; }, sso_registration_enabled: () => { // TODO From d619cc113578a848be9cd3171c1e67ac04c06a23 Mon Sep 17 00:00:00 2001 From: Matthias Mair Date: Thu, 9 Jan 2025 01:43:08 +0100 Subject: [PATCH 077/156] re-implement password change --- src/frontend/src/enums/ApiEndpoints.tsx | 2 +- .../src/pages/Auth/ChangePassword.tsx | 42 +++++++++++++++---- 2 files changed, 36 insertions(+), 8 deletions(-) diff --git a/src/frontend/src/enums/ApiEndpoints.tsx b/src/frontend/src/enums/ApiEndpoints.tsx index 1cedc5bde2c6..dbf7cb94af93 100644 --- a/src/frontend/src/enums/ApiEndpoints.tsx +++ b/src/frontend/src/enums/ApiEndpoints.tsx @@ -18,7 +18,7 @@ export enum ApiEndpoints { user_simple_login = 'email/generate/', user_reset = 'auth/password/reset/', // TODO change user_reset_set = 'auth/password/reset/confirm/', // TODO change - user_change_password = 'auth/password/change/', // TODO change + user_change_password = 'auth/v1/account/password/change', user_sso = 'auth/v1/account/providers', user_login = 'auth/v1/auth/login', user_login_mfa = 'auth/v1/auth/2fa/authenticate', diff --git a/src/frontend/src/pages/Auth/ChangePassword.tsx b/src/frontend/src/pages/Auth/ChangePassword.tsx index 6b6a2b96cd3c..57ef993db99d 100644 --- a/src/frontend/src/pages/Auth/ChangePassword.tsx +++ b/src/frontend/src/pages/Auth/ChangePassword.tsx @@ -19,12 +19,14 @@ import { StylishText } from '../../components/items/StylishText'; import { ProtectedRoute } from '../../components/nav/Layout'; import { LanguageContext } from '../../contexts/LanguageContext'; import { ApiEndpoints } from '../../enums/ApiEndpoints'; +import { clearCsrfCookie } from '../../functions/auth'; import { apiUrl } from '../../states/ApiState'; import { useUserState } from '../../states/UserState'; export default function Set_Password() { const simpleForm = useForm({ initialValues: { + current_password: '', new_password1: '', new_password2: '' } @@ -37,6 +39,7 @@ export default function Set_Password() { let message: any = values?.new_password2 || values?.new_password1 || + values?.current_password || values?.error || t`Password could not be changed`; @@ -55,27 +58,45 @@ export default function Set_Password() { } function handleSet() { + const { clearUserState } = useUserState.getState(); + + // check if passwords match + if (simpleForm.values.new_password1 !== simpleForm.values.new_password2) { + passwordError({ new_password2: t`Passwords do not match` }); + return; + } + // Set password with call to backend api .post(apiUrl(ApiEndpoints.user_change_password), { - new_password1: simpleForm.values.new_password1, - new_password2: simpleForm.values.new_password2 + current_password: simpleForm.values.current_password, + new_password: simpleForm.values.new_password2 }) .then((val) => { - if (val.status === 200) { + passwordError(val.data); + }) + .catch((err) => { + if (err.status === 401) { notifications.show({ title: t`Password Changed`, message: t`The password was set successfully. You can now login with your new password`, color: 'green', autoClose: false }); + clearUserState(); + clearCsrfCookie(); navigate('/login'); } else { - passwordError(val.data); + // compile errors + const errors: { [key: string]: string[] } = {}; + for (const val of err.response.data.errors) { + if (!errors[val.param]) { + errors[val.param] = []; + } + errors[val.param].push(val.message); + } + passwordError(errors); } - }) - .catch((err) => { - passwordError(err.response.data); }); } @@ -97,6 +118,13 @@ export default function Set_Password() { )} + Date: Thu, 9 Jan 2025 01:49:19 +0100 Subject: [PATCH 078/156] adjust tests --- src/frontend/src/pages/Auth/ChangePassword.tsx | 2 +- src/frontend/src/states/ApiState.tsx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/frontend/src/pages/Auth/ChangePassword.tsx b/src/frontend/src/pages/Auth/ChangePassword.tsx index 57ef993db99d..555a20a839e2 100644 --- a/src/frontend/src/pages/Auth/ChangePassword.tsx +++ b/src/frontend/src/pages/Auth/ChangePassword.tsx @@ -62,7 +62,7 @@ export default function Set_Password() { // check if passwords match if (simpleForm.values.new_password1 !== simpleForm.values.new_password2) { - passwordError({ new_password2: t`Passwords do not match` }); + passwordError({ new_password2: t`The two password fields didn’t match` }); return; } diff --git a/src/frontend/src/states/ApiState.tsx b/src/frontend/src/states/ApiState.tsx index d231220ca57f..52b93ae7a64e 100644 --- a/src/frontend/src/states/ApiState.tsx +++ b/src/frontend/src/states/ApiState.tsx @@ -58,7 +58,7 @@ export const useServerApiState = create()( }, registration_enabled: () => { // TODO - return true; + return false; }, sso_registration_enabled: () => { // TODO From ec6ee2cbedb5ad84668f5d00650c6cdd0b4f3c18 Mon Sep 17 00:00:00 2001 From: Matthias Mair Date: Thu, 9 Jan 2025 20:06:56 +0100 Subject: [PATCH 079/156] fix asserts --- src/frontend/src/pages/Auth/ChangePassword.tsx | 1 + src/frontend/tests/pui_login.spec.ts | 5 +---- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/src/frontend/src/pages/Auth/ChangePassword.tsx b/src/frontend/src/pages/Auth/ChangePassword.tsx index 555a20a839e2..d603eef55d2d 100644 --- a/src/frontend/src/pages/Auth/ChangePassword.tsx +++ b/src/frontend/src/pages/Auth/ChangePassword.tsx @@ -37,6 +37,7 @@ export default function Set_Password() { function passwordError(values: any) { let message: any = + values?.new_password || values?.new_password2 || values?.new_password1 || values?.current_password || diff --git a/src/frontend/tests/pui_login.spec.ts b/src/frontend/tests/pui_login.spec.ts index 55c7b43a2197..833c234e0c02 100644 --- a/src/frontend/tests/pui_login.spec.ts +++ b/src/frontend/tests/pui_login.spec.ts @@ -103,6 +103,7 @@ test('Login - Change Password', async ({ page }) => { await page.getByLabel('action-menu-user-actions-change-password').click(); // First attempt with some errors + await page.getByLabel('password', { exact: true }).fill('youshallnotpass'); await page.getByLabel('input-password-1').fill('12345'); await page.getByLabel('input-password-2').fill('54321'); await page.getByRole('button', { name: 'Confirm' }).click(); @@ -121,9 +122,5 @@ test('Login - Change Password', async ({ page }) => { await page.getByText('Password Changed').waitFor(); await page.getByText('The password was set successfully').waitFor(); - // Should have redirected to the index page - await page.waitForURL('**/platform/home**'); - await page.getByText('InvenTree Demo Server - Norman Nothington'); - await page.waitForTimeout(1000); }); From 5661b93910e02862f59ad0115008da007ba1277d Mon Sep 17 00:00:00 2001 From: Matthias Mair Date: Fri, 10 Jan 2025 02:52:05 +0100 Subject: [PATCH 080/156] align names with allauth --- .../components/forms/AuthenticationForm.tsx | 14 ++++---- src/frontend/src/enums/ApiEndpoints.tsx | 20 +++++------ src/frontend/src/functions/auth.tsx | 8 ++--- .../src/pages/Auth/ChangePassword.tsx | 2 +- .../AccountSettings/SecurityContent.tsx | 35 ++++++++++--------- src/frontend/src/states/ApiState.tsx | 12 +++---- src/frontend/src/states/states.tsx | 2 +- 7 files changed, 48 insertions(+), 45 deletions(-) diff --git a/src/frontend/src/components/forms/AuthenticationForm.tsx b/src/frontend/src/components/forms/AuthenticationForm.tsx index e8e432c50f38..d8eea8d392ed 100644 --- a/src/frontend/src/components/forms/AuthenticationForm.tsx +++ b/src/frontend/src/components/forms/AuthenticationForm.tsx @@ -35,9 +35,9 @@ export function AuthenticationForm() { }); const simpleForm = useForm({ initialValues: { email: '' } }); const [classicLoginMode, setMode] = useDisclosure(true); - const [auth_settings, sso_enabled, password_forgotten_enabled] = + const [auth_config, sso_enabled, password_forgotten_enabled] = useServerApiState((state) => [ - state.auth_settings, + state.auth_config, state.sso_enabled, state.password_forgotten_enabled ]); @@ -107,7 +107,7 @@ export function AuthenticationForm() { {sso_enabled() ? ( <> - {auth_settings?.socialaccount.providers.map((provider) => ( + {auth_config?.socialaccount.providers.map((provider) => ( ))} @@ -205,9 +205,9 @@ export function RegistrationForm() { } }); const navigate = useNavigate(); - const [auth_settings, registration_enabled, sso_registration] = + const [auth_config, registration_enabled, sso_registration] = useServerApiState((state) => [ - state.auth_settings, + state.auth_config, state.registration_enabled, state.sso_registration_enabled ]); @@ -228,7 +228,7 @@ export function RegistrationForm() { await ensureCsrf(); api - .post(apiUrl(ApiEndpoints.user_register), vals, { + .post(apiUrl(ApiEndpoints.auth_signup), vals, { headers: { Authorization: '' } }) .then((ret) => { @@ -321,7 +321,7 @@ export function RegistrationForm() { )} {sso_registration() && ( - {auth_settings?.socialaccount.providers.map((provider) => ( + {auth_config?.socialaccount.providers.map((provider) => ( ))} diff --git a/src/frontend/src/enums/ApiEndpoints.tsx b/src/frontend/src/enums/ApiEndpoints.tsx index dbf7cb94af93..8898e02bb534 100644 --- a/src/frontend/src/enums/ApiEndpoints.tsx +++ b/src/frontend/src/enums/ApiEndpoints.tsx @@ -18,15 +18,16 @@ export enum ApiEndpoints { user_simple_login = 'email/generate/', user_reset = 'auth/password/reset/', // TODO change user_reset_set = 'auth/password/reset/confirm/', // TODO change - user_change_password = 'auth/v1/account/password/change', - user_sso = 'auth/v1/account/providers', - user_login = 'auth/v1/auth/login', - user_login_mfa = 'auth/v1/auth/2fa/authenticate', - user_logout = 'auth/v1/auth/session', - user_register = 'auth/v1/auth/signup', - user_mfa = 'auth/v1/account/authenticators', - user_emails = 'auth/v1/account/email', - login_provider_redirect = 'auth/v1/auth/provider/redirect', + auth_pwd_change = 'auth/v1/account/password/change', + auth_login = 'auth/v1/auth/login', + auth_login_2fa = 'auth/v1/auth/2fa/authenticate', + auth_session = 'auth/v1/auth/session', + auth_signup = 'auth/v1/auth/signup', + auth_authenticators = 'auth/v1/account/authenticators', + auth_email = 'auth/v1/account/email', + auth_providers = 'auth/v1/account/providers', + auth_provider_redirect = 'auth/v1/auth/provider/redirect', + auth_config = 'auth/v1/config', // Generic API endpoints currency_list = 'currency/exchange/', @@ -50,7 +51,6 @@ export enum ApiEndpoints { icons = 'icons/', selectionlist_list = 'selection/', selectionlist_detail = 'selection/:id/', - securtiy_settings = 'auth/v1/config', // Barcode API endpoints barcode = 'barcode/', diff --git a/src/frontend/src/functions/auth.tsx b/src/frontend/src/functions/auth.tsx index 56c24abf34bc..0878ff0993a5 100644 --- a/src/frontend/src/functions/auth.tsx +++ b/src/frontend/src/functions/auth.tsx @@ -74,7 +74,7 @@ export const doBasicLogin = async ( clearCsrfCookie(); await ensureCsrf(); - const login_url = apiUrl(ApiEndpoints.user_login); + const login_url = apiUrl(ApiEndpoints.auth_login); let loginDone = false; let success = false; @@ -129,7 +129,7 @@ export const doLogout = async (navigate: NavigateFunction) => { // Logout from the server session if (isLoggedIn() || !!getCsrfCookie()) { - await authApi(apiUrl(ApiEndpoints.user_logout), undefined, 'delete').catch( + await authApi(apiUrl(ApiEndpoints.auth_session), undefined, 'delete').catch( () => {} ); showLoginNotification({ @@ -203,7 +203,7 @@ export function handleMfaLogin( values: { code: string } ) { const { setToken } = useUserState.getState(); - authApi(apiUrl(ApiEndpoints.user_login_mfa), undefined, 'post', { + authApi(apiUrl(ApiEndpoints.auth_login_2fa), undefined, 'post', { code: values.code }).then((response) => { setToken(response.data.meta.access_token); @@ -300,7 +300,7 @@ export function ProviderLogin( process: process, csrfmiddlewaretoken: getCsrfCookie() }; - const url = `${host}${apiUrl(ApiEndpoints.login_provider_redirect)}`; + const url = `${host}${apiUrl(ApiEndpoints.auth_provider_redirect)}`; post(url, values); } diff --git a/src/frontend/src/pages/Auth/ChangePassword.tsx b/src/frontend/src/pages/Auth/ChangePassword.tsx index d603eef55d2d..4e2087501c4b 100644 --- a/src/frontend/src/pages/Auth/ChangePassword.tsx +++ b/src/frontend/src/pages/Auth/ChangePassword.tsx @@ -69,7 +69,7 @@ export default function Set_Password() { // Set password with call to backend api - .post(apiUrl(ApiEndpoints.user_change_password), { + .post(apiUrl(ApiEndpoints.auth_pwd_change), { current_password: simpleForm.values.current_password, new_password: simpleForm.values.new_password2 }) diff --git a/src/frontend/src/pages/Index/Settings/AccountSettings/SecurityContent.tsx b/src/frontend/src/pages/Index/Settings/AccountSettings/SecurityContent.tsx index 35250150f662..2ac832a3d9c3 100644 --- a/src/frontend/src/pages/Index/Settings/AccountSettings/SecurityContent.tsx +++ b/src/frontend/src/pages/Index/Settings/AccountSettings/SecurityContent.tsx @@ -22,12 +22,14 @@ import { YesNoButton } from '../../../../components/buttons/YesNoButton'; import { ApiEndpoints } from '../../../../enums/ApiEndpoints'; import { ProviderLogin, authApi } from '../../../../functions/auth'; import { apiUrl, useServerApiState } from '../../../../states/ApiState'; -import type { Provider, SecuritySetting } from '../../../../states/states'; +import type { AuthConfig, Provider } from '../../../../states/states'; export function SecurityContent() { - const [auth_settings, sso_enabled, mfa_enabled] = useServerApiState( - (state) => [state.auth_settings, state.sso_enabled, state.mfa_enabled] - ); + const [auth_config, sso_enabled, mfa_enabled] = useServerApiState((state) => [ + state.auth_config, + state.sso_enabled, + state.mfa_enabled + ]); return ( @@ -39,7 +41,7 @@ export function SecurityContent() { Single Sign On Accounts {sso_enabled() ? ( - + ) : ( } @@ -81,7 +83,7 @@ function EmailContent() { const { isLoading, data, refetch } = useQuery({ queryKey: ['emails'], queryFn: () => - authApi(apiUrl(ApiEndpoints.user_emails)).then((res) => res.data.data) + authApi(apiUrl(ApiEndpoints.auth_email)).then((res) => res.data.data) }); function runServerAction( @@ -90,7 +92,7 @@ function EmailContent() { ) { const vals: any = data || { email: value }; console.log('vals', vals); - authApi(apiUrl(ApiEndpoints.user_emails), undefined, action, vals) + authApi(apiUrl(ApiEndpoints.auth_email), undefined, action, vals) .then(() => { refetch(); }) @@ -198,18 +200,18 @@ function ProviderButton({ provider }: Readonly<{ provider: Provider }>) { } function SsoContent({ - auth_settings -}: Readonly<{ auth_settings: SecuritySetting | undefined }>) { + auth_config +}: Readonly<{ auth_config: AuthConfig | undefined }>) { const [value, setValue] = useState(''); const [currentProviders, setCurrentProviders] = useState(); const { isLoading, data, refetch } = useQuery({ queryKey: ['sso-list'], queryFn: () => - authApi(apiUrl(ApiEndpoints.user_sso)).then((res) => res.data.data) + authApi(apiUrl(ApiEndpoints.auth_providers)).then((res) => res.data.data) }); useEffect(() => { - if (auth_settings === undefined) return; + if (auth_config === undefined) return; if (data === undefined) return; const configuredProviders = data.map((item: any) => { @@ -220,16 +222,15 @@ function SsoContent({ } // remove providers that are used currently - const newData = - auth_settings.socialaccount.providers.filter(isAlreadyInUse); + const newData = auth_config.socialaccount.providers.filter(isAlreadyInUse); setCurrentProviders(newData); - }, [auth_settings, data]); + }, [auth_config, data]); function removeProvider() { const split = value.split('$'); const provider = split[split.length - 1]; const uid = split.slice(0, split.length - 1).join('$'); - authApi(apiUrl(ApiEndpoints.user_sso), undefined, 'delete', { + authApi(apiUrl(ApiEndpoints.auth_providers), undefined, 'delete', { provider: provider, account: uid }) @@ -303,7 +304,9 @@ function MfaContent() { const { isLoading, data, refetch } = useQuery({ queryKey: ['mfa-list'], queryFn: () => - api.get(apiUrl(ApiEndpoints.user_mfa)).then((res) => res.data.data) + api + .get(apiUrl(ApiEndpoints.auth_authenticators)) + .then((res) => res.data.data) }); function parseDate(date: number) { diff --git a/src/frontend/src/states/ApiState.tsx b/src/frontend/src/states/ApiState.tsx index 52b93ae7a64e..c4e71e8f7f25 100644 --- a/src/frontend/src/states/ApiState.tsx +++ b/src/frontend/src/states/ApiState.tsx @@ -4,13 +4,13 @@ import { createJSONStorage, persist } from 'zustand/middleware'; import { api } from '../App'; import { emptyServerAPI } from '../defaults/defaults'; import { ApiEndpoints } from '../enums/ApiEndpoints'; -import type { SecuritySetting, ServerAPIProps } from './states'; +import type { AuthConfig, ServerAPIProps } from './states'; interface ServerApiStateProps { server: ServerAPIProps; setServer: (newServer: ServerAPIProps) => void; fetchServerApiState: () => void; - auth_settings?: SecuritySetting; + auth_config?: AuthConfig; sso_enabled: () => boolean; mfa_enabled: () => boolean; registration_enabled: () => boolean; @@ -37,19 +37,19 @@ export const useServerApiState = create()( // Fetch login/SSO behaviour await api - .get(apiUrl(ApiEndpoints.securtiy_settings), { + .get(apiUrl(ApiEndpoints.auth_config), { headers: { Authorization: '' } }) .then((response) => { - set({ auth_settings: response.data.data }); + set({ auth_config: response.data.data }); }) .catch(() => { console.error('ERR: Error fetching SSO information'); }); }, - auth_settings: undefined, + auth_config: undefined, sso_enabled: () => { - const data = get().auth_settings?.socialaccount.providers; + const data = get().auth_config?.socialaccount.providers; return !(data === undefined || data.length == 0); }, mfa_enabled: () => { diff --git a/src/frontend/src/states/states.tsx b/src/frontend/src/states/states.tsx index c6b1fb26f1be..5fceab96620e 100644 --- a/src/frontend/src/states/states.tsx +++ b/src/frontend/src/states/states.tsx @@ -50,7 +50,7 @@ export interface ServerAPIProps { django_admin: null | string; } -export interface SecuritySetting { +export interface AuthConfig { account: { authentication_method: string; }; From bf82c4c269efc15dfeade7fd76d675676f16941e Mon Sep 17 00:00:00 2001 From: Matthias Mair Date: Fri, 10 Jan 2025 02:52:28 +0100 Subject: [PATCH 081/156] simplify --- src/frontend/src/functions/auth.tsx | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/frontend/src/functions/auth.tsx b/src/frontend/src/functions/auth.tsx index 0878ff0993a5..ec7ff86b0aa7 100644 --- a/src/frontend/src/functions/auth.tsx +++ b/src/frontend/src/functions/auth.tsx @@ -74,15 +74,13 @@ export const doBasicLogin = async ( clearCsrfCookie(); await ensureCsrf(); - const login_url = apiUrl(ApiEndpoints.auth_login); - let loginDone = false; let success = false; // Attempt login with await api .post( - login_url, + apiUrl(ApiEndpoints.auth_login), { username: username, password: password From fc09af58b194f2a36a685e43f239c4d007812c26 Mon Sep 17 00:00:00 2001 From: Matthias Mair Date: Fri, 10 Jan 2025 03:09:47 +0100 Subject: [PATCH 082/156] refactor and rephrasing --- .../AccountSettings/SecurityContent.tsx | 84 ++++++++----------- 1 file changed, 33 insertions(+), 51 deletions(-) diff --git a/src/frontend/src/pages/Index/Settings/AccountSettings/SecurityContent.tsx b/src/frontend/src/pages/Index/Settings/AccountSettings/SecurityContent.tsx index 2ac832a3d9c3..a27b8f813aac 100644 --- a/src/frontend/src/pages/Index/Settings/AccountSettings/SecurityContent.tsx +++ b/src/frontend/src/pages/Index/Settings/AccountSettings/SecurityContent.tsx @@ -15,7 +15,7 @@ import { } from '@mantine/core'; import { IconAlertCircle, IconAt } from '@tabler/icons-react'; import { useQuery } from '@tanstack/react-query'; -import { useEffect, useMemo, useState } from 'react'; +import { useMemo, useState } from 'react'; import { api } from '../../../../App'; import { YesNoButton } from '../../../../components/buttons/YesNoButton'; @@ -36,12 +36,12 @@ export function SecurityContent() { <Trans>Email</Trans> - + - <Trans>Single Sign On Accounts</Trans> + <Trans>Single Sign On</Trans> {sso_enabled() ? ( - + ) : ( } @@ -52,11 +52,10 @@ export function SecurityContent() { )} - <Trans>Multifactor</Trans> + <Trans>Multifactor authentication</Trans> - {mfa_enabled() ? ( - + ) : ( } @@ -68,16 +67,15 @@ export function SecurityContent() { )} - - <Trans>Token</Trans> + <Trans>Access Tokens</Trans> - + ); } -function EmailContent() { +function EmailSection() { const [value, setValue] = useState(''); const [newEmailValue, setNewEmailValue] = useState(''); const { isLoading, data, refetch } = useQuery({ @@ -91,7 +89,6 @@ function EmailContent() { data?: any ) { const vals: any = data || { email: value }; - console.log('vals', vals); authApi(apiUrl(ApiEndpoints.auth_email), undefined, action, vals) .then(() => { refetch(); @@ -199,39 +196,29 @@ function ProviderButton({ provider }: Readonly<{ provider: Provider }>) { ); } -function SsoContent({ +function ProviderSection({ auth_config }: Readonly<{ auth_config: AuthConfig | undefined }>) { const [value, setValue] = useState(''); - const [currentProviders, setCurrentProviders] = useState(); const { isLoading, data, refetch } = useQuery({ - queryKey: ['sso-list'], + queryKey: ['provider-list'], queryFn: () => authApi(apiUrl(ApiEndpoints.auth_providers)).then((res) => res.data.data) }); - useEffect(() => { - if (auth_config === undefined) return; - if (data === undefined) return; + const availableProviders = useMemo(() => { + if (!auth_config || !data) return []; - const configuredProviders = data.map((item: any) => { - return item.provider.id; - }); - function isAlreadyInUse(value: any) { - return !configuredProviders.includes(value.id); - } - - // remove providers that are used currently - const newData = auth_config.socialaccount.providers.filter(isAlreadyInUse); - setCurrentProviders(newData); + const configuredProviders = data.map((item: any) => item.provider.id); + return auth_config.socialaccount.providers.filter( + (provider: any) => !configuredProviders.includes(provider.id) + ); }, [auth_config, data]); function removeProvider() { - const split = value.split('$'); - const provider = split[split.length - 1]; - const uid = split.slice(0, split.length - 1).join('$'); + const [uid, provider] = value.split('$'); authApi(apiUrl(ApiEndpoints.auth_providers), undefined, 'delete', { - provider: provider, + provider, account: uid }) .then(() => { @@ -240,7 +227,6 @@ function SsoContent({ .catch((res) => console.log(res.data)); } - /* renderer */ if (isLoading) return ; return ( @@ -252,9 +238,7 @@ function SsoContent({ title={t`Not configured`} color='yellow' > - - There are no social network accounts connected to this account.{' '} - + There are no providers connected to this account. ) : ( @@ -262,7 +246,7 @@ function SsoContent({ value={value} onChange={setValue} name='sso_accounts' - label={t`You can sign in to your account using any of the following third party accounts`} + label={t`You can sign in to your account using any of the following providers`} > {data.map((link: any) => ( @@ -275,7 +259,7 @@ function SsoContent({ )} @@ -284,11 +268,11 @@ function SsoContent({ Add SSO Account - {currentProviders === undefined ? ( + {availableProviders === undefined ? ( Loading ) : ( - {currentProviders.map((provider: any) => ( + {availableProviders.map((provider: any) => ( ))} @@ -300,8 +284,8 @@ function SsoContent({ ); } -function MfaContent() { - const { isLoading, data, refetch } = useQuery({ +function MfaSection() { + const { isLoading, data } = useQuery({ queryKey: ['mfa-list'], queryFn: () => api @@ -309,12 +293,11 @@ function MfaContent() { .then((res) => res.data.data) }); - function parseDate(date: number) { - if (date == null) return 'Never'; - return new Date(date * 1000).toLocaleString(); - } + const parseDate = (date: number) => + date == null ? 'Never' : new Date(date * 1000).toLocaleString(); + const rows = useMemo(() => { - if (isLoading || data === undefined) return null; + if (isLoading || !data) return null; return data.map((token: any) => ( {token.type} @@ -324,7 +307,6 @@ function MfaContent() { )); }, [data, isLoading]); - /* renderer */ if (isLoading) return ; if (data.length == 0) @@ -354,7 +336,7 @@ function MfaContent() { ); } -function TokenContent() { +function TokenSection() { const { isLoading, data, refetch } = useQuery({ queryKey: ['token-list'], queryFn: () => @@ -369,8 +351,9 @@ function TokenContent() { }) .catch((res) => console.log(res.data)); } + const rows = useMemo(() => { - if (isLoading || data === undefined) return null; + if (isLoading || !data) return null; return data.map((token: any) => ( @@ -397,7 +380,6 @@ function TokenContent() { )); }, [data, isLoading]); - /* renderer */ if (isLoading) return ; if (data.length == 0) From 56137d268bcd9c3fff4f4c6315e050510c4f58dd Mon Sep 17 00:00:00 2001 From: Matthias Mair Date: Fri, 10 Jan 2025 03:16:16 +0100 Subject: [PATCH 083/156] fix nesting issue --- .../AccountSettings/SecurityContent.tsx | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/src/frontend/src/pages/Index/Settings/AccountSettings/SecurityContent.tsx b/src/frontend/src/pages/Index/Settings/AccountSettings/SecurityContent.tsx index a27b8f813aac..7ed59d349424 100644 --- a/src/frontend/src/pages/Index/Settings/AccountSettings/SecurityContent.tsx +++ b/src/frontend/src/pages/Index/Settings/AccountSettings/SecurityContent.tsx @@ -267,17 +267,17 @@ function ProviderSection({ Add SSO Account - - {availableProviders === undefined ? ( + {availableProviders === undefined ? ( + Loading - ) : ( - - {availableProviders.map((provider: any) => ( - - ))} - - )} - + + ) : ( + + {availableProviders.map((provider: any) => ( + + ))} + + )} From 72f89eaf15e10ff8c8bd8a85c438f21c1c7eb11c Mon Sep 17 00:00:00 2001 From: Matthias Mair Date: Fri, 10 Jan 2025 03:31:37 +0100 Subject: [PATCH 084/156] clean up urls even more --- src/backend/InvenTree/InvenTree/urls.py | 10 ++++---- src/backend/InvenTree/users/api.py | 31 ------------------------- src/backend/InvenTree/users/test_api.py | 9 ------- 3 files changed, 5 insertions(+), 45 deletions(-) diff --git a/src/backend/InvenTree/InvenTree/urls.py b/src/backend/InvenTree/InvenTree/urls.py index 5bd6d00992ae..9c3e37da00f6 100644 --- a/src/backend/InvenTree/InvenTree/urls.py +++ b/src/backend/InvenTree/InvenTree/urls.py @@ -94,7 +94,6 @@ path( 'auth/', include([ - path('logout/', users.api.Logout.as_view(), name='api-logout'), path( 'login-redirect/', users.api.LoginRedirect.as_view(), @@ -105,10 +104,9 @@ include( (build_urlpatterns(Client.BROWSER), 'headless'), namespace='browser' ), - ), + ), # Allauth headless logic (only the browser client is included as we only use sessions based auth there) ]), ), - path('_allauth/', include('allauth.headless.urls')), # Magic login URLs path( 'email/generate/', @@ -122,8 +120,10 @@ backendpatterns = [ - path('auth/', include('rest_framework.urls', namespace='rest_framework')), - path('auth/', auth_request), + path( + 'auth/', include('rest_framework.urls', namespace='rest_framework') + ), # Used for (DRF) browsable API auth + path('auth/', auth_request), # Used for proxies to check if user is authenticated path('api/', include(apipatterns)), path('api-doc/', SpectacularRedocView.as_view(url_name='schema'), name='api-doc'), ] diff --git a/src/backend/InvenTree/users/api.py b/src/backend/InvenTree/users/api.py index 96ae6095ae09..f55a0c29ac06 100644 --- a/src/backend/InvenTree/users/api.py +++ b/src/backend/InvenTree/users/api.py @@ -9,7 +9,6 @@ from django.views.generic.base import RedirectView import structlog -from drf_spectacular.utils import OpenApiResponse, extend_schema, extend_schema_view from rest_framework import exceptions, permissions from rest_framework.generics import DestroyAPIView from rest_framework.permissions import IsAuthenticated @@ -216,36 +215,6 @@ class GroupList(GroupMixin, ListCreateAPI): ordering_fields = ['name'] -@extend_schema_view( - post=extend_schema( - responses={200: OpenApiResponse(description='User successfully logged out')} - ) -) -class Logout(APIView): - """API view for logging out via API.""" - - serializer_class = None - - def post(self, request): - """Logout the current user. - - Deletes user token associated with request. - """ - from InvenTree.middleware import get_token_from_request - - if request.user: - token_key = get_token_from_request(request) - - if token_key: - try: - token = ApiToken.objects.get(key=token_key, user=request.user) - token.delete() - except ApiToken.DoesNotExist: # pragma: no cover - pass - - return super().logout(request) - - class GetAuthToken(APIView): """Return authentication token for an authenticated user.""" diff --git a/src/backend/InvenTree/users/test_api.py b/src/backend/InvenTree/users/test_api.py index b57b97e9655b..a19107526f86 100644 --- a/src/backend/InvenTree/users/test_api.py +++ b/src/backend/InvenTree/users/test_api.py @@ -83,15 +83,6 @@ def test_group_api(self): self.assertIn('name', response.data) self.assertIn('permissions', response.data) - # def test_logout(self): - # """Test api logout endpoint.""" - # token_key = self.get(url=reverse('api-token')).data['token'] - # self.client.logout() - # self.client.credentials(HTTP_AUTHORIZATION='Token ' + token_key) - - # self.post(reverse('api-logout'), expected_code=200) - # self.get(reverse('api-token'), expected_code=401) - def test_login_redirect(self): """Test login redirect endpoint.""" response = self.get(reverse('api-login-redirect'), expected_code=302) From 6fe06b536fb793b0df0984dacb2ce110778ceb51 Mon Sep 17 00:00:00 2001 From: Matthias Mair Date: Sun, 12 Jan 2025 04:58:22 +0100 Subject: [PATCH 085/156] add mfa add and remove screens --- src/frontend/src/enums/ApiEndpoints.tsx | 4 + .../AccountSettings/SecurityContent.tsx | 347 ++++++++++++++++-- .../Settings/AccountSettings/useConfirm.tsx | 105 ++++++ 3 files changed, 429 insertions(+), 27 deletions(-) create mode 100644 src/frontend/src/pages/Index/Settings/AccountSettings/useConfirm.tsx diff --git a/src/frontend/src/enums/ApiEndpoints.tsx b/src/frontend/src/enums/ApiEndpoints.tsx index 8898e02bb534..f7c4026374e6 100644 --- a/src/frontend/src/enums/ApiEndpoints.tsx +++ b/src/frontend/src/enums/ApiEndpoints.tsx @@ -24,6 +24,10 @@ export enum ApiEndpoints { auth_session = 'auth/v1/auth/session', auth_signup = 'auth/v1/auth/signup', auth_authenticators = 'auth/v1/account/authenticators', + auth_recovery = 'auth/v1/account/authenticators/recovery-codes', + auth_mfa_reauthenticate = 'auth/v1/auth/2fa/reauthenticate', + auth_totp = 'auth/v1/account/authenticators/totp', + auth_reauthenticate = 'auth/v1/auth/reauthenticate', auth_email = 'auth/v1/account/email', auth_providers = 'auth/v1/account/providers', auth_provider_redirect = 'auth/v1/auth/provider/redirect', diff --git a/src/frontend/src/pages/Index/Settings/AccountSettings/SecurityContent.tsx b/src/frontend/src/pages/Index/Settings/AccountSettings/SecurityContent.tsx index 7ed59d349424..13b958a81955 100644 --- a/src/frontend/src/pages/Index/Settings/AccountSettings/SecurityContent.tsx +++ b/src/frontend/src/pages/Index/Settings/AccountSettings/SecurityContent.tsx @@ -3,9 +3,11 @@ import { Alert, Badge, Button, + Code, Grid, Group, Loader, + Modal, Radio, Stack, Table, @@ -13,16 +15,19 @@ import { TextInput, Title } from '@mantine/core'; -import { IconAlertCircle, IconAt } from '@tabler/icons-react'; +import { useDisclosure } from '@mantine/hooks'; +import { showNotification } from '@mantine/notifications'; +import { IconAlertCircle, IconAt, IconX } from '@tabler/icons-react'; import { useQuery } from '@tanstack/react-query'; import { useMemo, useState } from 'react'; - import { api } from '../../../../App'; +import { QRCode } from '../../../../components/barcodes/QRCode'; import { YesNoButton } from '../../../../components/buttons/YesNoButton'; import { ApiEndpoints } from '../../../../enums/ApiEndpoints'; import { ProviderLogin, authApi } from '../../../../functions/auth'; import { apiUrl, useServerApiState } from '../../../../states/ApiState'; import type { AuthConfig, Provider } from '../../../../states/states'; +import { useReauth } from './useConfirm'; export function SecurityContent() { const [auth_config, sso_enabled, mfa_enabled] = useServerApiState((state) => [ @@ -34,7 +39,7 @@ export function SecurityContent() { return ( - <Trans>Email</Trans> + <Trans>Email Addresses</Trans> @@ -63,7 +68,7 @@ export function SecurityContent() { color='yellow' > <Trans> - Multifactor authentication is not configured for your account{' '} + Multifactor authentication is not enabled for this server </Trans> </Alert> )} @@ -103,7 +108,7 @@ function EmailSection() { <Grid.Col span={6}> {data.length == 0 ? ( <Text> - <Trans>Currently no emails are registered</Trans> + <Trans>Currently no email adreesses are registered</Trans> </Text> ) : ( <Radio.Group @@ -285,7 +290,15 @@ function ProviderSection({ } function MfaSection() { - const { isLoading, data } = useQuery({ + const [getReauthText, ReauthModal] = useReauth(); + const [recoveryCodes, setRecoveryCodes] = useState< + Recoverycodes | undefined + >(); + const [ + recoveryCodesOpen, + { open: openRecoveryCodes, close: closeRecoveryCodes } + ] = useDisclosure(false); + const { isLoading, data, refetch } = useQuery({ queryKey: ['mfa-list'], queryFn: () => api @@ -293,6 +306,36 @@ function MfaSection() { .then((res) => res.data.data) }); + function showRecoveryCodes(codes: Recoverycodes) { + setRecoveryCodes(codes); + openRecoveryCodes(); + } + + const removeTotp = () => { + runActionWithFallback( + () => + authApi(apiUrl(ApiEndpoints.auth_totp), undefined, 'delete').then( + () => { + refetch(); + return ResultType.success; + } + ), + getReauthText + ); + }; + const viewRecoveryCodes = () => { + runActionWithFallback( + () => + authApi(apiUrl(ApiEndpoints.auth_recovery), undefined, 'get').then( + (res) => { + showRecoveryCodes(res.data.data); + return ResultType.success; + } + ), + getReauthText + ); + }; + const parseDate = (date: number) => date == null ? 'Never' : new Date(date * 1000).toLocaleString(); @@ -303,39 +346,289 @@ function MfaSection() { <Table.Td>{token.type}</Table.Td> <Table.Td>{parseDate(token.last_used_at)}</Table.Td> <Table.Td>{parseDate(token.created_at)}</Table.Td> + <Table.Td> + {token.type == 'totp' && ( + <Button color='red' onClick={removeTotp}> + <Trans>Remove</Trans> + </Button> + )} + {token.type == 'recovery_codes' && ( + <Button onClick={viewRecoveryCodes}> + <Trans>View</Trans> + </Button> + )} + </Table.Td> </Table.Tr> )); }, [data, isLoading]); + const usedFactors: string[] = useMemo(() => { + if (isLoading || !data) return []; + return data.map((token: any) => token.type); + }, [data]); + if (isLoading) return <Loader />; - if (data.length == 0) - return ( - <Alert icon={<IconAlertCircle size='1rem' />} color='green'> - <Trans>No factors configured</Trans> - </Alert> + return ( + <> + <ReauthModal /> + <Grid> + <Grid.Col span={6}> + {data.length == 0 ? ( + <Alert icon={<IconAlertCircle size='1rem' />} color='yellow'> + <Trans>No factors configured</Trans> + </Alert> + ) : ( + <Table stickyHeader striped highlightOnHover withTableBorder> + <Table.Thead> + <Table.Tr> + <Table.Th> + <Trans>Type</Trans> + </Table.Th> + <Table.Th> + <Trans>Last used at</Trans> + </Table.Th> + <Table.Th> + <Trans>Created at</Trans> + </Table.Th> + <Table.Th> + <Trans>Actions</Trans> + </Table.Th> + </Table.Tr> + </Table.Thead> + <Table.Tbody>{rows}</Table.Tbody> + </Table> + )} + </Grid.Col> + <Grid.Col span={6}> + <MfaAddSection + usedFactors={usedFactors} + refetch={refetch} + showRecoveryCodes={showRecoveryCodes} + /> + <Modal + opened={recoveryCodesOpen} + onClose={() => { + refetch(); + closeRecoveryCodes(); + }} + title={t`Recovery Codes`} + centered + > + <Title order={3}> + <Trans>Unused Codes</Trans> + + {recoveryCodes?.unused_codes?.join('\n')} + + + <Trans>Used Codes</Trans> + + {recoveryCodes?.used_codes?.join('\n')} + +
+ + + ); +} + +enum ResultType { + success = 0, + reauth = 1, + mfareauth = 2, + error = 3 +} + +export interface Recoverycodes { + type: string; + created_at: number; + last_used_at: null; + total_code_count: number; + unused_code_count: number; + unused_codes: string[]; + used_code_count: number; + used_codes: string[]; +} + +function MfaAddSection({ + usedFactors, + refetch, + showRecoveryCodes +}: Readonly<{ + usedFactors: string[]; + refetch: () => void; + showRecoveryCodes: (codes: Recoverycodes) => void; +}>) { + const [totpQrOpen, { open: openTotpQr, close: closeTotpQr }] = + useDisclosure(false); + const [totpQr, setTotpQr] = useState<{ totp_url: string; secret: string }>(); + const [value, setValue] = useState(''); + const [getReauthText, ReauthModal] = useReauth(); + + const registerRecoveryCodes = async () => { + await runActionWithFallback( + () => + authApi(apiUrl(ApiEndpoints.auth_recovery), undefined, 'post') + .then((res) => { + showRecoveryCodes(res.data.data); + return ResultType.success; + }) + .catch((err) => { + showNotification({ + title: t`Error while registering recovery codes`, + message: err.response.data.errors + .map((error: any) => error.message) + .join('\n'), + color: 'red', + icon: + }); + + return ResultType.error; + }), + getReauthText + ); + }; + const registerTotp = async () => { + await runActionWithFallback( + () => + authApi(apiUrl(ApiEndpoints.auth_totp), undefined, 'get') + .then(() => ResultType.error) + .catch((err) => { + if (err.status == 404 && err.response.data.meta.secret) { + setTotpQr(err.response.data.meta); + openTotpQr(); + return ResultType.success; + } + return ResultType.error; + }), + getReauthText ); + }; + + const possibleFactors = useMemo(() => { + return [ + { + type: 'totp', + name: t`TOTP`, + description: t`Time-based One-Time Password`, + function: registerTotp, + used: usedFactors?.includes('totp') + }, + { + type: 'recovery_codes', + name: t`Recovery Codes`, + description: t`One-Time pre-generated recovery codes`, + function: registerRecoveryCodes, + used: usedFactors?.includes('recovery_codes') + } + ]; + }, [usedFactors]); return ( - - - - - Type - - - Last used at - - - Created at - - - - {rows} -
+ + + Add Factor + {possibleFactors.map((factor) => ( + + ))} + + + + + Secret +
+ {totpQr?.secret} +
+ setValue(event.currentTarget.value)} + /> + +
+
+
); } +async function runActionWithFallback( + action: () => Promise, + getReauthText: (props: any) => any +) { + const rslt = await action().catch((err) => { + // check if we need to re-authenticate + if (err.status == 401) { + if ( + err.response.data.data.flows.find( + (flow: any) => flow.id == 'mfa_reauthenticate' + ) + ) { + return ResultType.mfareauth; + } else if ( + err.response.data.data.flows.find( + (flow: any) => flow.id == 'reauthenticate' + ) + ) { + return ResultType.reauth; + } else { + return ResultType.error; + } + } else { + return ResultType.error; + } + }); + if (rslt == ResultType.mfareauth) { + authApi(apiUrl(ApiEndpoints.auth_mfa_reauthenticate), undefined, 'post', { + code: await getReauthText({ + label: t`TOTP Code`, + name: 'TOTP', + description: t`Enter your TOTP or recovery code` + }) + }).then(() => { + action(); + }); + } else if (rslt == ResultType.reauth) { + authApi(apiUrl(ApiEndpoints.auth_reauthenticate), undefined, 'post', { + password: await getReauthText({ + label: t`Password`, + name: 'password', + description: t`Enter your password` + }) + }).then(() => { + action(); + }); + } +} + function TokenSection() { const { isLoading, data, refetch } = useQuery({ queryKey: ['token-list'], diff --git a/src/frontend/src/pages/Index/Settings/AccountSettings/useConfirm.tsx b/src/frontend/src/pages/Index/Settings/AccountSettings/useConfirm.tsx new file mode 100644 index 000000000000..6d18389e68a0 --- /dev/null +++ b/src/frontend/src/pages/Index/Settings/AccountSettings/useConfirm.tsx @@ -0,0 +1,105 @@ +import { Trans, t } from '@lingui/macro'; +import { Button, Group, Modal, Stack, TextInput } from '@mantine/core'; +import { useState } from 'react'; + +/* Adapted from https://daveteu.medium.com/react-custom-confirmation-box-458cceba3f7b */ +const createPromise = () => { + let resolver: any; + return [ + new Promise((resolve, reject) => { + resolver = resolve; + }), + resolver + ]; +}; + +/* Adapted from https://daveteu.medium.com/react-custom-confirmation-box-458cceba3f7b */ +export const useConfirm = () => { + const [open, setOpen] = useState(false); + const [resolver, setResolver] = useState({ resolver: null }); + const [label, setLabel] = useState(''); + + const getConfirmation = async (text: string) => { + setLabel(text); + setOpen(true); + const [promise, resolve] = await createPromise(); + + setResolver({ resolve }); + return promise; + }; + + const onClick = async (status: boolean) => { + setOpen(false); + resolver.resolve(status); + }; + + const Confirmation = () => ( + setOpen(false)}> + {label} + + + + ); + + return [getConfirmation, Confirmation]; +}; + +type InputProps = { + label: string; + name: string; + description: string; +}; +export const useReauth = () => { + const [inputProps, setInputProps] = useState({ + label: '', + name: '', + description: '' + }); + + const [value, setValue] = useState(''); + const [open, setOpen] = useState(false); + const [resolver, setResolver] = useState({ resolver: null }); + + const getReauthText = async (props: InputProps) => { + setInputProps(props); + setOpen(true); + const [promise, resolve] = await createPromise(); + + setResolver({ resolve }); + return promise; + }; + + const onClick = async (result: string, positive: boolean) => { + setOpen(false); + resolver.resolve(result, positive); + }; + + const ReauthModal = () => ( + setOpen(false)} + title={t`Reauthentication`} + > + + setValue(event.currentTarget.value)} + /> + + + + + + + ); + + return [getReauthText, ReauthModal]; +}; From 95d3a954c254988ba99f560915671504f9cc4e27 Mon Sep 17 00:00:00 2001 From: Matthias Mair Date: Sun, 12 Jan 2025 15:05:18 +0100 Subject: [PATCH 086/156] add type --- .../Settings/AccountSettings/useConfirm.tsx | 25 +++++++++++++------ 1 file changed, 18 insertions(+), 7 deletions(-) diff --git a/src/frontend/src/pages/Index/Settings/AccountSettings/useConfirm.tsx b/src/frontend/src/pages/Index/Settings/AccountSettings/useConfirm.tsx index 6d18389e68a0..d7872a197a2a 100644 --- a/src/frontend/src/pages/Index/Settings/AccountSettings/useConfirm.tsx +++ b/src/frontend/src/pages/Index/Settings/AccountSettings/useConfirm.tsx @@ -6,7 +6,7 @@ import { useState } from 'react'; const createPromise = () => { let resolver: any; return [ - new Promise((resolve, reject) => { + new Promise((resolve) => { resolver = resolve; }), resolver @@ -16,7 +16,9 @@ const createPromise = () => { /* Adapted from https://daveteu.medium.com/react-custom-confirmation-box-458cceba3f7b */ export const useConfirm = () => { const [open, setOpen] = useState(false); - const [resolver, setResolver] = useState({ resolver: null }); + const [resolver, setResolver] = useState<((status: boolean) => void) | null>( + null + ); const [label, setLabel] = useState(''); const getConfirmation = async (text: string) => { @@ -24,13 +26,15 @@ export const useConfirm = () => { setOpen(true); const [promise, resolve] = await createPromise(); - setResolver({ resolve }); + setResolver(resolve); return promise; }; const onClick = async (status: boolean) => { setOpen(false); - resolver.resolve(status); + if (resolver) { + resolver(status); + } }; const Confirmation = () => ( @@ -49,7 +53,10 @@ type InputProps = { name: string; description: string; }; -export const useReauth = () => { +export const useReauth = (): [ + (props: InputProps) => Promise<[string, boolean]>, + () => JSX.Element +] => { const [inputProps, setInputProps] = useState({ label: '', name: '', @@ -58,7 +65,9 @@ export const useReauth = () => { const [value, setValue] = useState(''); const [open, setOpen] = useState(false); - const [resolver, setResolver] = useState({ resolver: null }); + const [resolver, setResolver] = useState<{ + resolve: (result: string, positive: boolean) => void; + } | null>(null); const getReauthText = async (props: InputProps) => { setInputProps(props); @@ -71,7 +80,9 @@ export const useReauth = () => { const onClick = async (result: string, positive: boolean) => { setOpen(false); - resolver.resolve(result, positive); + if (resolver) { + resolver.resolve(result, positive); + } }; const ReauthModal = () => ( From 08c458f26e865c394c6618d6d6f2daf43055cce5 Mon Sep 17 00:00:00 2001 From: Matthias Mair Date: Sun, 12 Jan 2025 15:20:11 +0100 Subject: [PATCH 087/156] revert dep change --- contrib/container/requirements.txt | 37 +++++++++++++++--------------- 1 file changed, 19 insertions(+), 18 deletions(-) diff --git a/contrib/container/requirements.txt b/contrib/container/requirements.txt index c648aa53847d..ffdef1596d34 100644 --- a/contrib/container/requirements.txt +++ b/contrib/container/requirements.txt @@ -209,24 +209,25 @@ typing-extensions==4.12.2 \ # via # psycopg # psycopg-pool -uv==0.5.11 \ - --hash=sha256:08c660c69e7dd874b52ad96b597b57e9f999659f3d9827cdbad884a68e48f7e9 \ - --hash=sha256:164e068ebdf1177c8863c870bb68e411105b44d53cd91e3b9d8f5fd9202420d8 \ - --hash=sha256:1fe74893f77f343a43bcfaee2600b63f99a26a82568cfe16d0d1b5a77d9b033f \ - --hash=sha256:398eb87ef23b0cd25a8bfcc0dddf0d360d92aba03f660962f447a6585ced440b \ - --hash=sha256:48a3bcbc480d5f922145cd2c68182dcb11effa3ca9f5a9ae9b2f6ce21f9ade32 \ - --hash=sha256:4bd0c2868dde8ddef89b9e33a85913e450bb71b834f6d73b525e450e840639c8 \ - --hash=sha256:6094ca4c5f917d58f884011416bb15066e222ef8d0494f26b0156ac97ad6810b \ - --hash=sha256:736c9b8c86b18eb4dded22cd0f61cc0302bf387de860806c6700b561a4bb95f9 \ - --hash=sha256:7d1e78c010cf112ddd02d704579e6501c3104a34c944c01f618fc417d6fd55a8 \ - --hash=sha256:7d2571f175ded2631220c4586e3e14e93952db4a681d0ca094e6cc4124001a83 \ - --hash=sha256:914dd829808e5d65bf261cbfbb8a01ee80f7d90bc8c9e54f2fc5aa2501f5eec1 \ - --hash=sha256:9c2d455db44cc5de70e359e88da9659397f52e78190a9a8922defdee7ed26787 \ - --hash=sha256:a2461a563e28b75cc3b396ed910feecac9518a90c49ac312b1a9da77bae10911 \ - --hash=sha256:bac233c1e3ae343d0904f78e4a18ca0b479d304aa8de2175df9d72b76dd7764e \ - --hash=sha256:cefa3ec37f83acdcb4b067ef09622a78e56a22fc6376f5705cd64435bc9bc280 \ - --hash=sha256:d24d4e816010b692d1180b69eb8aef1d16657a43b5e2edab8be71a2e700ccf9f \ - --hash=sha256:e1f6a7d727e86deb67d0a4df669de8c03033cd19ed23d27c7113abd7cb0b9bd7 +uv==0.5.7 \ + --hash=sha256:071b57c934bdee8d7502a70e9ea0739a10e9b2d1d0c67e923a09e7a23d9a181b \ + --hash=sha256:13961a8116515eb288c4f91849fba11ebda0dfeec44cc356e388b3b03b2dbbe1 \ + --hash=sha256:1c5b89c64fb627f52f1e9c9bbc4dcc7bae29c4c5ab8eff46da3c966bbd4caed2 \ + --hash=sha256:27c630780e1856a70fbeb267e1ed6835268a1b50963ab9a984fafa4184389def \ + --hash=sha256:46b03a9a78438219fb3060c096773284e2f22417a9c1f8fdd602f0650b3355c2 \ + --hash=sha256:4d22a5046a6246af85c92257d110ed8fbcd98b16824e4efa9d825d001222b2cb \ + --hash=sha256:737a06b15c4e6b8ab7dd0a577ba766380bda4c18ba4ecfcfff37d336f1b03a00 \ + --hash=sha256:747c011da9f631354a1c89b62b19b8572e040d3fe01c6fb8d650facc7a09fdbb \ + --hash=sha256:76b514c79136e779cccf90cce5d60f317a0d42074e9f4c059f198ef435f2f6ab \ + --hash=sha256:78c3c040e52c09a410b9788656d6e760d557f223058537081cb03a3e25ce89de \ + --hash=sha256:a141b40444c4184efba9fdc10abb3c1cff32154c7f8b0ad46ddc180d65a82d90 \ + --hash=sha256:a45648db157d2aaff859fe71ec738efea09b972b8864feb2fd61ef856a15b24f \ + --hash=sha256:a4fc62749bda8e7ae62212b1d85cdf6c7bad41918b3c8ac5a6d730dd093d793d \ + --hash=sha256:b79e32438390add793bebc41b0729054e375be30bc53f124ee212d9c97affc39 \ + --hash=sha256:ba25eb99891b95b5200d5e369b788d443fae370b097e7268a71e9ba753f2af3f \ + --hash=sha256:c1e7b5bcc8b380e333e948c01f6f4c6203067b5de60a05f8ed786332af7a9132 \ + --hash=sha256:d0600d2b2fbd9a9446bfbb7f03d88bc3d0293b949ce40e326429dd4fe246c926 \ + --hash=sha256:fb4a3ccbe13072b98919413ac8378dd3e2b5480352f75c349a4f71f423801485 # via -r contrib/container/requirements.in wheel==0.45.1 \ --hash=sha256:661e1abd9198507b1409a20c02106d9670b2576e916d58f520316666abca6729 \ From b6c4b9923a2fe5fab6fd5c3dab2c80b310a46b02 Mon Sep 17 00:00:00 2001 From: Matthias Mair Date: Sun, 12 Jan 2025 15:33:18 +0100 Subject: [PATCH 088/156] fix api version --- src/backend/InvenTree/InvenTree/api_version.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/backend/InvenTree/InvenTree/api_version.py b/src/backend/InvenTree/InvenTree/api_version.py index d1341fad58a3..4e9766bd70f5 100644 --- a/src/backend/InvenTree/InvenTree/api_version.py +++ b/src/backend/InvenTree/InvenTree/api_version.py @@ -1,13 +1,13 @@ """InvenTree API version information.""" # InvenTree API version -INVENTREE_API_VERSION = 299 +INVENTREE_API_VERSION = 300 """Increment this API version number whenever there is a significant change to the API that any clients need to know about.""" INVENTREE_API_TEXT = """ -v299 - 2025-01-10 : https://github.com/inventree/InvenTree/pull/6293 +v300 - 2025-01-10 : https://github.com/inventree/InvenTree/pull/6293 - Removes a considerable amount of old auth endpoints - Introduces allauth based REST API From cf6eb26c1d12fb0483e654a28abd4710aa13d4ce Mon Sep 17 00:00:00 2001 From: Matthias Mair Date: Sun, 12 Jan 2025 15:43:05 +0100 Subject: [PATCH 089/156] re-add settings --- src/backend/InvenTree/InvenTree/settings.py | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/src/backend/InvenTree/InvenTree/settings.py b/src/backend/InvenTree/InvenTree/settings.py index dee0b7e64403..d4d42b209e54 100644 --- a/src/backend/InvenTree/InvenTree/settings.py +++ b/src/backend/InvenTree/InvenTree/settings.py @@ -549,6 +549,16 @@ 'rest_framework.renderers.BrowsableAPIRenderer' ) +# JWT settings - rest_framework_simplejwt +USE_JWT = get_boolean_setting('INVENTREE_USE_JWT', 'use_jwt', False) +if USE_JWT: + JWT_AUTH_COOKIE = 'inventree-auth' + JWT_AUTH_REFRESH_COOKIE = 'inventree-token' + REST_FRAMEWORK['DEFAULT_AUTHENTICATION_CLASSES'].append( + 'dj_rest_auth.jwt_auth.JWTCookieAuthentication' + ) + INSTALLED_APPS.append('rest_framework_simplejwt') + # WSGI default setting WSGI_APPLICATION = 'InvenTree.wsgi.application' @@ -887,6 +897,7 @@ # as well Q_CLUSTER['django_redis'] = 'worker' + SILENCED_SYSTEM_CHECKS = ['templates.E003', 'templates.W003'] # Password validation @@ -1209,6 +1220,7 @@ if CORS_ALLOWED_ORIGIN_REGEXES: logger.info('CORS: Whitelisted origin regexes: %s', CORS_ALLOWED_ORIGIN_REGEXES) +# region auth for app in SOCIAL_BACKENDS: # Ensure that the app starts with 'allauth.socialaccount.providers' social_prefix = 'allauth.socialaccount.providers.' @@ -1294,6 +1306,11 @@ HEADLESS_TOKEN_STRATEGY = 'InvenTree.auth_overrides.DRFTokenStrategy' MFA_ENABLED = get_boolean_setting('INVENTREE_MFA_ENABLED', 'mfa_enabled', True) +LOGOUT_REDIRECT_URL = get_setting( + 'INVENTREE_LOGOUT_REDIRECT_URL', 'logout_redirect_url', 'index' +) +# endregion auth + # Markdownify configuration # Ref: https://django-markdownify.readthedocs.io/en/latest/settings.html From d94a521e5c7057ca478cdb3fddf3f18c60b28d2d Mon Sep 17 00:00:00 2001 From: Matthias Mair Date: Sun, 12 Jan 2025 15:43:19 +0100 Subject: [PATCH 090/156] simplify urls --- src/backend/InvenTree/InvenTree/urls.py | 20 +++++++------------- 1 file changed, 7 insertions(+), 13 deletions(-) diff --git a/src/backend/InvenTree/InvenTree/urls.py b/src/backend/InvenTree/InvenTree/urls.py index 9c3e37da00f6..5040bd7110a3 100644 --- a/src/backend/InvenTree/InvenTree/urls.py +++ b/src/backend/InvenTree/InvenTree/urls.py @@ -124,6 +124,12 @@ 'auth/', include('rest_framework.urls', namespace='rest_framework') ), # Used for (DRF) browsable API auth path('auth/', auth_request), # Used for proxies to check if user is authenticated + path('accounts/', include('allauth.urls')), + path( + 'accounts/login/', + RedirectView.as_view(url=f'/{settings.FRONTEND_URL_BASE}', permanent=False), + name='account_login', + ), # Add a redirect for login views path('api/', include(apipatterns)), path('api-doc/', SpectacularRedocView.as_view(url_name='schema'), name='api-doc'), ] @@ -139,19 +145,7 @@ ] urlpatterns += backendpatterns - -frontendpatterns = [ - *platform_urls, - path('accounts/', include('allauth.urls')), # Still needed for provider login - # Add a redirect for login views - path( - 'accounts/login/', - RedirectView.as_view(url=f'/{settings.FRONTEND_URL_BASE}', permanent=False), - name='account_login', - ), -] - -urlpatterns += frontendpatterns +urlpatterns += platform_urls # Append custom plugin URLs (if custom plugin support is enabled) if settings.PLUGINS_ENABLED: From 7b23b73188fcbd789aa430bf0ad7cb112b801eff Mon Sep 17 00:00:00 2001 From: Matthias Mair Date: Mon, 13 Jan 2025 00:07:54 +0100 Subject: [PATCH 091/156] Add timeout to login wait for --- src/frontend/playwright.config.ts | 2 +- src/frontend/tests/login.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/frontend/playwright.config.ts b/src/frontend/playwright.config.ts index 67d243a111b4..67ab21c0ad22 100644 --- a/src/frontend/playwright.config.ts +++ b/src/frontend/playwright.config.ts @@ -5,7 +5,7 @@ export default defineConfig({ fullyParallel: true, timeout: 90000, forbidOnly: !!process.env.CI, - retries: process.env.CI ? 2 : 0, + retries: process.env.CI ? 3 : 0, workers: process.env.CI ? 3 : undefined, reporter: process.env.CI ? [['html', { open: 'never' }], ['github']] : 'list', diff --git a/src/frontend/tests/login.ts b/src/frontend/tests/login.ts index 8f81e059e2a7..5a8052bd735f 100644 --- a/src/frontend/tests/login.ts +++ b/src/frontend/tests/login.ts @@ -34,7 +34,7 @@ export const doQuickLogin = async ( await page.goto(`${url}/login/?login=${username}&password=${password}`); await page.waitForURL('**/platform/home'); - await page.getByLabel('navigation-menu').waitFor(); + await page.getByLabel('navigation-menu').waitFor({ timeout: 5000 }); await page.getByText(/InvenTree Demo Server -/).waitFor(); }; From 4fa066049357e20e5c6cea415e4f20662c573458 Mon Sep 17 00:00:00 2001 From: Matthias Mair Date: Mon, 13 Jan 2025 08:13:44 +0100 Subject: [PATCH 092/156] fix url assertation --- src/frontend/tests/baseFixtures.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/frontend/tests/baseFixtures.ts b/src/frontend/tests/baseFixtures.ts index 8d3f3feded4a..f7885824a363 100644 --- a/src/frontend/tests/baseFixtures.ts +++ b/src/frontend/tests/baseFixtures.ts @@ -73,6 +73,7 @@ export const test = baseTest.extend({ url != 'http://localhost:8000/api/user/token/' && url != 'http://localhost:8000/api/auth/v1/auth/login' && url != 'http://localhost:8000/api/auth/v1/auth/session' && + url != 'http://localhost:8000/api/auth/v1/account/password/change' && url != 'http://localhost:8000/api/barcode/' && url != 'https://docs.inventree.org/en/versions.json' && url != 'http://localhost:5173/favicon.ico' && From d2c9519a688a330103f66f643628636bd4499da8 Mon Sep 17 00:00:00 2001 From: Matthias Mair Date: Mon, 13 Jan 2025 08:25:21 +0100 Subject: [PATCH 093/156] remove unneded mfa_enabled --- .../AccountSettings/SecurityContent.tsx | 19 +++---------------- src/frontend/src/states/ApiState.tsx | 5 ----- 2 files changed, 3 insertions(+), 21 deletions(-) diff --git a/src/frontend/src/pages/Index/Settings/AccountSettings/SecurityContent.tsx b/src/frontend/src/pages/Index/Settings/AccountSettings/SecurityContent.tsx index 13b958a81955..f1655e34d5a8 100644 --- a/src/frontend/src/pages/Index/Settings/AccountSettings/SecurityContent.tsx +++ b/src/frontend/src/pages/Index/Settings/AccountSettings/SecurityContent.tsx @@ -30,10 +30,9 @@ import type { AuthConfig, Provider } from '../../../../states/states'; import { useReauth } from './useConfirm'; export function SecurityContent() { - const [auth_config, sso_enabled, mfa_enabled] = useServerApiState((state) => [ + const [auth_config, sso_enabled] = useServerApiState((state) => [ state.auth_config, - state.sso_enabled, - state.mfa_enabled + state.sso_enabled ]); return ( @@ -59,19 +58,7 @@ export function SecurityContent() { <Trans>Multifactor authentication</Trans> - {mfa_enabled() ? ( - - ) : ( - } - title={t`Not enabled`} - color='yellow' - > - - Multifactor authentication is not enabled for this server - - - )} + <Trans>Access Tokens</Trans> diff --git a/src/frontend/src/states/ApiState.tsx b/src/frontend/src/states/ApiState.tsx index c4e71e8f7f25..757baad1c72b 100644 --- a/src/frontend/src/states/ApiState.tsx +++ b/src/frontend/src/states/ApiState.tsx @@ -12,7 +12,6 @@ interface ServerApiStateProps { fetchServerApiState: () => void; auth_config?: AuthConfig; sso_enabled: () => boolean; - mfa_enabled: () => boolean; registration_enabled: () => boolean; sso_registration_enabled: () => boolean; password_forgotten_enabled: () => boolean; @@ -52,10 +51,6 @@ export const useServerApiState = create()( const data = get().auth_config?.socialaccount.providers; return !(data === undefined || data.length == 0); }, - mfa_enabled: () => { - // TODO - return true; - }, registration_enabled: () => { // TODO return false; From 4b412f57161277eff3d996f805477976587d18e2 Mon Sep 17 00:00:00 2001 From: Matthias Mair Date: Mon, 13 Jan 2025 23:08:24 +0100 Subject: [PATCH 094/156] add setting for configuring types --- docs/docs/start/config.md | 1 + src/backend/InvenTree/InvenTree/settings.py | 12 ++++++++++-- .../Settings/AccountSettings/SecurityContent.tsx | 7 +++++-- 3 files changed, 16 insertions(+), 4 deletions(-) diff --git a/docs/docs/start/config.md b/docs/docs/start/config.md index 4ac937e651b1..6e9f57235a8f 100644 --- a/docs/docs/start/config.md +++ b/docs/docs/start/config.md @@ -379,6 +379,7 @@ InvenTree provides allowance for additional sign-in options. The following optio | Environment Variable | Configuration File | Description | Default | | --- | --- | --- | --- | | INVENTREE_MFA_ENABLED | mfa_enabled | Enable or disable multi-factor authentication support for the InvenTree server | True | +| MFA_SUPPORTED_TYPES | mfa_supported_types | List of supported multi-factor authentication types | recovery_codes,totp | ### Single Sign On diff --git a/src/backend/InvenTree/InvenTree/settings.py b/src/backend/InvenTree/InvenTree/settings.py index d4d42b209e54..075381d75196 100644 --- a/src/backend/InvenTree/InvenTree/settings.py +++ b/src/backend/InvenTree/InvenTree/settings.py @@ -1220,7 +1220,7 @@ if CORS_ALLOWED_ORIGIN_REGEXES: logger.info('CORS: Whitelisted origin regexes: %s', CORS_ALLOWED_ORIGIN_REGEXES) -# region auth +# region auth for app in SOCIAL_BACKENDS: # Ensure that the app starts with 'allauth.socialaccount.providers' social_prefix = 'allauth.socialaccount.providers.' @@ -1304,7 +1304,15 @@ } HEADLESS_ONLY = True HEADLESS_TOKEN_STRATEGY = 'InvenTree.auth_overrides.DRFTokenStrategy' -MFA_ENABLED = get_boolean_setting('INVENTREE_MFA_ENABLED', 'mfa_enabled', True) +MFA_ENABLED = get_boolean_setting( + 'INVENTREE_MFA_ENABLED', 'mfa_enabled', True +) # TODO re-implement +MFA_SUPPORTED_TYPES = get_setting( + 'INVENTREE_MFA_SUPPORTED_TYPES', + 'mfa_supported_types', + ['totp', 'recovery_codes'], + typecast=list, +) LOGOUT_REDIRECT_URL = get_setting( 'INVENTREE_LOGOUT_REDIRECT_URL', 'logout_redirect_url', 'index' diff --git a/src/frontend/src/pages/Index/Settings/AccountSettings/SecurityContent.tsx b/src/frontend/src/pages/Index/Settings/AccountSettings/SecurityContent.tsx index f1655e34d5a8..0099f5cf75f1 100644 --- a/src/frontend/src/pages/Index/Settings/AccountSettings/SecurityContent.tsx +++ b/src/frontend/src/pages/Index/Settings/AccountSettings/SecurityContent.tsx @@ -445,6 +445,7 @@ function MfaAddSection({ refetch: () => void; showRecoveryCodes: (codes: Recoverycodes) => void; }>) { + const [auth_config] = useServerApiState((state) => [state.auth_config]); const [totpQrOpen, { open: openTotpQr, close: closeTotpQr }] = useDisclosure(false); const [totpQr, setTotpQr] = useState<{ totp_url: string; secret: string }>(); @@ -507,8 +508,10 @@ function MfaAddSection({ function: registerRecoveryCodes, used: usedFactors?.includes('recovery_codes') } - ]; - }, [usedFactors]); + ].filter((factor) => { + auth_config?.mfa.supported_types.includes(factor.type); + }); + }, [usedFactors, auth_config]); return ( From d413d114289fbd29ac5f0ccee045dcd6a9b26872 Mon Sep 17 00:00:00 2001 From: Matthias Mair Date: Mon, 13 Jan 2025 23:08:53 +0100 Subject: [PATCH 095/156] bump api version --- src/backend/InvenTree/InvenTree/api_version.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/backend/InvenTree/InvenTree/api_version.py b/src/backend/InvenTree/InvenTree/api_version.py index 55ceaeafd0c8..523c47730ff6 100644 --- a/src/backend/InvenTree/InvenTree/api_version.py +++ b/src/backend/InvenTree/InvenTree/api_version.py @@ -1,13 +1,13 @@ """InvenTree API version information.""" # InvenTree API version -INVENTREE_API_VERSION = 300 +INVENTREE_API_VERSION = 301 """Increment this API version number whenever there is a significant change to the API that any clients need to know about.""" INVENTREE_API_TEXT = """ -v300 - 2025-01-10 : https://github.com/inventree/InvenTree/pull/6293 +v301 - 2025-01-10 : https://github.com/inventree/InvenTree/pull/6293 - Removes a considerable amount of old auth endpoints - Introduces allauth based REST API From 47ece105e5f40f33c397e493e30477d746827710 Mon Sep 17 00:00:00 2001 From: Matthias Mair Date: Sun, 19 Jan 2025 23:44:43 +0100 Subject: [PATCH 096/156] fix password reset flow --- src/frontend/src/enums/ApiEndpoints.tsx | 4 +- src/frontend/src/functions/auth.tsx | 10 +++- src/frontend/src/pages/Auth/ResetPassword.tsx | 52 +++++++++---------- 3 files changed, 36 insertions(+), 30 deletions(-) diff --git a/src/frontend/src/enums/ApiEndpoints.tsx b/src/frontend/src/enums/ApiEndpoints.tsx index f7c4026374e6..2f87246336c1 100644 --- a/src/frontend/src/enums/ApiEndpoints.tsx +++ b/src/frontend/src/enums/ApiEndpoints.tsx @@ -16,8 +16,8 @@ export enum ApiEndpoints { user_token = 'user/token/', user_tokens = 'user/tokens/', user_simple_login = 'email/generate/', - user_reset = 'auth/password/reset/', // TODO change - user_reset_set = 'auth/password/reset/confirm/', // TODO change + user_reset = 'auth/v1/auth/password/request', + user_reset_set = 'auth/v1/auth/password/reset', auth_pwd_change = 'auth/v1/account/password/change', auth_login = 'auth/v1/auth/login', auth_login_2fa = 'auth/v1/auth/2fa/authenticate', diff --git a/src/frontend/src/functions/auth.tsx b/src/frontend/src/functions/auth.tsx index ec7ff86b0aa7..655a50772330 100644 --- a/src/frontend/src/functions/auth.tsx +++ b/src/frontend/src/functions/auth.tsx @@ -172,10 +172,16 @@ export function handleReset( navigate: NavigateFunction, values: { email: string } ) { + ensureCsrf(); api - .post(apiUrl(ApiEndpoints.user_reset), values, { + .post( + apiUrl(ApiEndpoints.user_reset), + values + /*{ headers: { Authorization: '' } - }) + } + */ + ) .then((val) => { if (val.status === 200) { notifications.show({ diff --git a/src/frontend/src/pages/Auth/ResetPassword.tsx b/src/frontend/src/pages/Auth/ResetPassword.tsx index ac1d5a136229..24a81aed3d06 100644 --- a/src/frontend/src/pages/Auth/ResetPassword.tsx +++ b/src/frontend/src/pages/Auth/ResetPassword.tsx @@ -22,32 +22,41 @@ export default function ResetPassword() { const [searchParams] = useSearchParams(); const navigate = useNavigate(); - const token = searchParams.get('token'); - const uid = searchParams.get('uid'); + const key = searchParams.get('key'); - function invalidToken() { + function invalidKey() { notifications.show({ - title: t`Token invalid`, - message: t`You need to provide a valid token to set a new password. Check your inbox for a reset link.`, + title: t`Key invalid`, + message: t`You need to provide a valid key to set a new password. Check your inbox for a reset link.`, color: 'red' }); navigate('/login'); } + function success() { + notifications.show({ + title: t`Password set`, + message: t`The password was set successfully. You can now login with your new password`, + color: 'green', + autoClose: false + }); + navigate('/login'); + } + function passwordError(values: any) { notifications.show({ title: t`Reset failed`, - message: values?.new_password2 || values?.new_password1 || values?.token, + message: values?.errors.map((e: any) => e.message).join('\n'), color: 'red' }); } useEffect(() => { - // make sure we have a token - if (!token || !uid) { - invalidToken(); + // make sure we have a key + if (!key) { + invalidKey(); } - }, [token]); + }, [key]); function handleSet() { // Set password with call to backend @@ -55,32 +64,23 @@ export default function ResetPassword() { .post( apiUrl(ApiEndpoints.user_reset_set), { - uid: uid, - token: token, - new_password1: simpleForm.values.password, - new_password2: simpleForm.values.password + key: key, + password: simpleForm.values.password }, { headers: { Authorization: '' } } ) .then((val) => { if (val.status === 200) { - notifications.show({ - title: t`Password set`, - message: t`The password was set successfully. You can now login with your new password`, - color: 'green', - autoClose: false - }); - navigate('/login'); + success(); } else { passwordError(val.data); } }) .catch((err) => { - if ( - err.response?.status === 400 && - err.response?.data?.token == 'Invalid value' - ) { - invalidToken(); + if (err.response?.status === 400) { + passwordError(err.response.data); + } else if (err.response?.status === 401) { + success(); } else { passwordError(err.response.data); } From 67d89b549fcd8bbe8802e6cae4422a8bd3fe0c1a Mon Sep 17 00:00:00 2001 From: Matthias Mair Date: Sun, 19 Jan 2025 23:57:25 +0100 Subject: [PATCH 097/156] change settings order --- src/backend/InvenTree/InvenTree/settings.py | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/src/backend/InvenTree/InvenTree/settings.py b/src/backend/InvenTree/InvenTree/settings.py index 37b92886f59d..65344dd0f2f0 100644 --- a/src/backend/InvenTree/InvenTree/settings.py +++ b/src/backend/InvenTree/InvenTree/settings.py @@ -1224,6 +1224,10 @@ if CORS_ALLOWED_ORIGIN_REGEXES: logger.info('CORS: Whitelisted origin regexes: %s', CORS_ALLOWED_ORIGIN_REGEXES) +# Load settings for the frontend interface +FRONTEND_SETTINGS = config.get_frontend_settings(debug=DEBUG) +FRONTEND_URL_BASE = FRONTEND_SETTINGS['base_url'] + # region auth for app in SOCIAL_BACKENDS: # Ensure that the app starts with 'allauth.socialaccount.providers' @@ -1302,7 +1306,9 @@ HEADLESS_FRONTEND_URLS = { 'account_confirm_email': 'http://localhost:8000/verify-email/{key}', # noqa: RUF027 'account_reset_password': 'http://localhost:8000/password-reset', - 'account_reset_password_from_key': 'http://localhost:8000/password-reset-key/{key}', # noqa: RUF027 + 'account_reset_password_from_key': 'http://localhost:8000/' + + FRONTEND_URL_BASE + + '/set-password?key={key}', # noqa: RUF027 'account_signup': 'http://localhost:8000/signup', 'socialaccount_login_error': 'http://localhost:8000/social-login-error', } @@ -1388,10 +1394,6 @@ 'INVENTREE_CUSTOMIZE', 'customize', default_value=None, typecast=dict ) -# Load settings for the frontend interface -FRONTEND_SETTINGS = config.get_frontend_settings(debug=DEBUG) -FRONTEND_URL_BASE = FRONTEND_SETTINGS['base_url'] - if DEBUG: logger.info('InvenTree running with DEBUG enabled') From 02491143223170ade6052cbd948bacc49950cf45 Mon Sep 17 00:00:00 2001 From: Matthias Mair Date: Mon, 20 Jan 2025 20:01:21 +0100 Subject: [PATCH 098/156] save auth context --- src/frontend/src/functions/auth.tsx | 48 +++++++++---------- .../AccountSettings/SecurityContent.tsx | 24 +++++++--- src/frontend/src/states/ApiState.tsx | 8 +++- src/frontend/src/states/states.tsx | 24 ++++++++++ 4 files changed, 71 insertions(+), 33 deletions(-) diff --git a/src/frontend/src/functions/auth.tsx b/src/frontend/src/functions/auth.tsx index 655a50772330..175ecc14b838 100644 --- a/src/frontend/src/functions/auth.tsx +++ b/src/frontend/src/functions/auth.tsx @@ -5,7 +5,7 @@ import type { AxiosRequestConfig } from 'axios'; import type { Location, NavigateFunction } from 'react-router-dom'; import { api, setApiDefaults } from '../App'; import { ApiEndpoints } from '../enums/ApiEndpoints'; -import { apiUrl } from '../states/ApiState'; +import { apiUrl, useServerApiState } from '../states/ApiState'; import { useLocalState } from '../states/LocalState'; import { useUserState } from '../states/UserState'; import { type Provider, fetchGlobalStates } from '../states/states'; @@ -66,6 +66,7 @@ export const doBasicLogin = async ( ) => { const { host } = useLocalState.getState(); const { clearUserState, setToken, fetchUserState } = useUserState.getState(); + const { setAuthContext } = useServerApiState.getState(); if (username.length == 0 || password.length == 0) { return; @@ -90,6 +91,7 @@ export const doBasicLogin = async ( } ) .then((response) => { + setAuthContext(response.data?.data); if (response.status == 200 && response.data?.meta?.is_authenticated) { setToken(response.data.meta.access_token); loginDone = true; @@ -98,6 +100,7 @@ export const doBasicLogin = async ( }) .catch((err) => { if (err?.response?.status == 401) { + setAuthContext(err.response.data?.data); const mfa_flow = err.response.data.data.flows.find( (flow: any) => flow.id == 'mfa_authenticate' ); @@ -173,32 +176,23 @@ export function handleReset( values: { email: string } ) { ensureCsrf(); - api - .post( - apiUrl(ApiEndpoints.user_reset), - values - /*{ - headers: { Authorization: '' } + api.post(apiUrl(ApiEndpoints.user_reset), values).then((val) => { + if (val.status === 200) { + notifications.show({ + title: t`Mail delivery successful`, + message: t`Check your inbox for a reset link. This only works if you have an account. Check in spam too.`, + color: 'green', + autoClose: false + }); + navigate('/login'); + } else { + notifications.show({ + title: t`Reset failed`, + message: t`Check your input and try again.`, + color: 'red' + }); } - */ - ) - .then((val) => { - if (val.status === 200) { - notifications.show({ - title: t`Mail delivery successful`, - message: t`Check your inbox for a reset link. This only works if you have an account. Check in spam too.`, - color: 'green', - autoClose: false - }); - navigate('/login'); - } else { - notifications.show({ - title: t`Reset failed`, - message: t`Check your input and try again.`, - color: 'red' - }); - } - }); + }); } export function handleMfaLogin( @@ -207,9 +201,11 @@ export function handleMfaLogin( values: { code: string } ) { const { setToken } = useUserState.getState(); + const { setAuthContext } = useServerApiState.getState(); authApi(apiUrl(ApiEndpoints.auth_login_2fa), undefined, 'post', { code: values.code }).then((response) => { + setAuthContext(response.data?.data); setToken(response.data.meta.access_token); followRedirect(navigate, location?.state); }); diff --git a/src/frontend/src/pages/Index/Settings/AccountSettings/SecurityContent.tsx b/src/frontend/src/pages/Index/Settings/AccountSettings/SecurityContent.tsx index 0099f5cf75f1..745e102bdcbb 100644 --- a/src/frontend/src/pages/Index/Settings/AccountSettings/SecurityContent.tsx +++ b/src/frontend/src/pages/Index/Settings/AccountSettings/SecurityContent.tsx @@ -574,7 +574,9 @@ async function runActionWithFallback( action: () => Promise, getReauthText: (props: any) => any ) { + const { setAuthContext } = useServerApiState.getState(); const rslt = await action().catch((err) => { + setAuthContext(err.response.data?.data); // check if we need to re-authenticate if (err.status == 401) { if ( @@ -603,9 +605,14 @@ async function runActionWithFallback( name: 'TOTP', description: t`Enter your TOTP or recovery code` }) - }).then(() => { - action(); - }); + }) + .then((response) => { + setAuthContext(response.data?.data); + action(); + }) + .catch((err) => { + setAuthContext(err.response.data?.data); + }); } else if (rslt == ResultType.reauth) { authApi(apiUrl(ApiEndpoints.auth_reauthenticate), undefined, 'post', { password: await getReauthText({ @@ -613,9 +620,14 @@ async function runActionWithFallback( name: 'password', description: t`Enter your password` }) - }).then(() => { - action(); - }); + }) + .then((response) => { + setAuthContext(response.data?.data); + action(); + }) + .catch((err) => { + setAuthContext(err.response.data?.data); + }); } } diff --git a/src/frontend/src/states/ApiState.tsx b/src/frontend/src/states/ApiState.tsx index 757baad1c72b..0df04318c660 100644 --- a/src/frontend/src/states/ApiState.tsx +++ b/src/frontend/src/states/ApiState.tsx @@ -4,13 +4,15 @@ import { createJSONStorage, persist } from 'zustand/middleware'; import { api } from '../App'; import { emptyServerAPI } from '../defaults/defaults'; import { ApiEndpoints } from '../enums/ApiEndpoints'; -import type { AuthConfig, ServerAPIProps } from './states'; +import type { AuthConfig, AuthContext, ServerAPIProps } from './states'; interface ServerApiStateProps { server: ServerAPIProps; setServer: (newServer: ServerAPIProps) => void; fetchServerApiState: () => void; auth_config?: AuthConfig; + auth_context?: AuthContext; + setAuthContext: (auth_context: AuthContext) => void; sso_enabled: () => boolean; registration_enabled: () => boolean; sso_registration_enabled: () => boolean; @@ -47,6 +49,10 @@ export const useServerApiState = create()( }); }, auth_config: undefined, + auth_context: undefined, + setAuthContext(auth_context) { + set({ auth_context }); + }, sso_enabled: () => { const data = get().auth_config?.socialaccount.providers; return !(data === undefined || data.length == 0); diff --git a/src/frontend/src/states/states.tsx b/src/frontend/src/states/states.tsx index 5fceab96620e..ea632d95a5a1 100644 --- a/src/frontend/src/states/states.tsx +++ b/src/frontend/src/states/states.tsx @@ -50,6 +50,30 @@ export interface ServerAPIProps { django_admin: null | string; } +export interface AuthContext { + status: number; + data: { flows: Flow[] }; + meta: { is_authenticated: boolean }; +} + +export enum FlowEnum { + VerifyEmail = 'verify_email', + Login = 'login', + Signup = 'signup', + ProviderRedirect = 'provider_redirect', + ProviderSignup = 'provider_signup', + ProviderToken = 'provider_token', + MfaAuthenticate = 'mfa_authenticate', + Reauthenticate = 'reauthenticate', + MfaReauthenticate = 'mfa_reauthenticate' +} + +export interface Flow { + id: FlowEnum; + providers?: string[]; + is_pending?: boolean[]; +} + export interface AuthConfig { account: { authentication_method: string; From 6bad3ff2ff13b21a4035993fcde8b32f61b0480a Mon Sep 17 00:00:00 2001 From: Matthias Mair Date: Mon, 20 Jan 2025 21:45:50 +0100 Subject: [PATCH 099/156] rename var to remove confusion --- src/frontend/src/components/panels/PanelGroup.tsx | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/frontend/src/components/panels/PanelGroup.tsx b/src/frontend/src/components/panels/PanelGroup.tsx index f857cccc339b..17e19e854494 100644 --- a/src/frontend/src/components/panels/PanelGroup.tsx +++ b/src/frontend/src/components/panels/PanelGroup.tsx @@ -116,20 +116,20 @@ function BasePanelGroup({ // Callback when the active panel changes const handlePanelChange = useCallback( - (panel: string, event?: any) => { + (targetPanel: string, event?: any) => { if (event && (event?.ctrlKey || event?.shiftKey)) { - const url = `${location.pathname}/../${panel}`; + const url = `${location.pathname}/../${targetPanel}`; cancelEvent(event); navigateToLink(url, navigate, event); } else { - navigate(`../${panel}`); + navigate(`../${targetPanel}`); } - localState.setLastUsedPanel(pageKey)(panel); + localState.setLastUsedPanel(pageKey)(targetPanel); // Optionally call external callback hook - if (panel && onPanelChange) { - onPanelChange(panel); + if (targetPanel && onPanelChange) { + onPanelChange(targetPanel); } }, [activePanels, navigate, location, onPanelChange] From 0c40cb4a5aa70b00d8d5f669904389d9376bea77 Mon Sep 17 00:00:00 2001 From: Matthias Mair Date: Mon, 20 Jan 2025 21:52:24 +0100 Subject: [PATCH 100/156] make login/register seperate paths --- .../src/components/forms/AuthenticationForm.tsx | 8 ++++---- src/frontend/src/pages/Auth/Login.tsx | 13 ++++++++++++- src/frontend/src/router.tsx | 1 + 3 files changed, 17 insertions(+), 5 deletions(-) diff --git a/src/frontend/src/components/forms/AuthenticationForm.tsx b/src/frontend/src/components/forms/AuthenticationForm.tsx index d8eea8d392ed..982b386265c1 100644 --- a/src/frontend/src/components/forms/AuthenticationForm.tsx +++ b/src/frontend/src/components/forms/AuthenticationForm.tsx @@ -332,10 +332,10 @@ export function RegistrationForm() { export function ModeSelector({ loginMode, - setMode + changePage }: Readonly<{ loginMode: boolean; - setMode: any; + changePage: (state: string) => void; }>) { const [sso_registration, registration_enabled] = useServerApiState( (state) => [state.sso_registration_enabled, state.registration_enabled] @@ -354,7 +354,7 @@ export function ModeSelector({ type='button' c='dimmed' size='xs' - onClick={() => setMode.close()} + onClick={() => changePage('register')} > Register @@ -365,7 +365,7 @@ export function ModeSelector({ type='button' c='dimmed' size='xs' - onClick={() => setMode.open()} + onClick={() => changePage('login')} > Go back to login diff --git a/src/frontend/src/pages/Auth/Login.tsx b/src/frontend/src/pages/Auth/Login.tsx index 139a4fdf650c..91e3fcabe477 100644 --- a/src/frontend/src/pages/Auth/Login.tsx +++ b/src/frontend/src/pages/Auth/Login.tsx @@ -39,6 +39,14 @@ export default function Login() { const location = useLocation(); const [searchParams] = useSearchParams(); + useEffect(() => { + if (location.pathname === '/register') { + setMode.close(); + } else { + setMode.open(); + } + }, [location]); + // Data manipulation functions function ChangeHost(newHost: string | null): void { if (newHost === null) return; @@ -95,7 +103,10 @@ export default function Login() { )} {loginMode ? : } - + navigate(`/${newPage}`)} + /> diff --git a/src/frontend/src/router.tsx b/src/frontend/src/router.tsx index 297660ba3de4..ad4a1b7fea2d 100644 --- a/src/frontend/src/router.tsx +++ b/src/frontend/src/router.tsx @@ -171,6 +171,7 @@ export const routes = (
}> } />, + } />, } />, } />, } /> From ec8267f3412d05c988c9a5ef850080e888cb07c9 Mon Sep 17 00:00:00 2001 From: Matthias Mair Date: Mon, 20 Jan 2025 21:56:47 +0100 Subject: [PATCH 101/156] make info text better --- .../Settings/AccountSettings/SecurityContent.tsx | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/src/frontend/src/pages/Index/Settings/AccountSettings/SecurityContent.tsx b/src/frontend/src/pages/Index/Settings/AccountSettings/SecurityContent.tsx index 745e102bdcbb..a17d55c091ed 100644 --- a/src/frontend/src/pages/Index/Settings/AccountSettings/SecurityContent.tsx +++ b/src/frontend/src/pages/Index/Settings/AccountSettings/SecurityContent.tsx @@ -94,9 +94,13 @@ function EmailSection() { {data.length == 0 ? ( - - Currently no email adreesses are registered - + } + title={t`Not configured`} + color='yellow' + > + Currently no email addresses are registered. + ) : ( - There are no providers connected to this account. + There are no providers connected to this account. ) : ( From 001ddf2cee639f67e1097f3012209f9fc9de6340 Mon Sep 17 00:00:00 2001 From: Matthias Mair Date: Mon, 20 Jan 2025 21:58:06 +0100 Subject: [PATCH 102/156] adjust urls --- src/backend/InvenTree/InvenTree/settings.py | 28 +++++++++++++++------ 1 file changed, 20 insertions(+), 8 deletions(-) diff --git a/src/backend/InvenTree/InvenTree/settings.py b/src/backend/InvenTree/InvenTree/settings.py index 65344dd0f2f0..b10d3bdff1c5 100644 --- a/src/backend/InvenTree/InvenTree/settings.py +++ b/src/backend/InvenTree/InvenTree/settings.py @@ -1302,16 +1302,28 @@ ACCOUNT_ADAPTER = 'InvenTree.auth_overrides.CustomAccountAdapter' ACCOUNT_LOGOUT_ON_PASSWORD_CHANGE = True -# TDOO: Implement dynamic lookup for those + +# region to_be_moved +def get_frontend_url(pui_path: str): + """Generate frontend url. + + #TODO This function should be moved to the adapter once https://codeberg.org/allauth/django-allauth/issues/4226 is resolved. + """ + host: str = 'http://localhost:8000' + if not host.endswith('/'): + host += '/' + return f'{host}{FRONTEND_URL_BASE}/{pui_path}' + + HEADLESS_FRONTEND_URLS = { - 'account_confirm_email': 'http://localhost:8000/verify-email/{key}', # noqa: RUF027 - 'account_reset_password': 'http://localhost:8000/password-reset', - 'account_reset_password_from_key': 'http://localhost:8000/' - + FRONTEND_URL_BASE - + '/set-password?key={key}', # noqa: RUF027 - 'account_signup': 'http://localhost:8000/signup', - 'socialaccount_login_error': 'http://localhost:8000/social-login-error', + 'account_confirm_email': get_frontend_url('verify-email/{key}'), # noqa: RUF027 + 'account_reset_password': get_frontend_url('reset-password'), + 'account_reset_password_from_key': get_frontend_url('set-password?key={key}'), # noqa: RUF027 + 'account_signup': get_frontend_url('register'), + 'socialaccount_login_error': get_frontend_url('social-login-error'), } +# endregion to_be_moved + HEADLESS_ONLY = True HEADLESS_TOKEN_STRATEGY = 'InvenTree.auth_overrides.DRFTokenStrategy' MFA_ENABLED = get_boolean_setting( From 0c86f2705f0bf5c12c9bfcc1d2d8511e9539d7b1 Mon Sep 17 00:00:00 2001 From: Matthias Mair Date: Mon, 20 Jan 2025 22:06:14 +0100 Subject: [PATCH 103/156] add error message --- .../AccountSettings/SecurityContent.tsx | 28 +++++++++++++++---- 1 file changed, 22 insertions(+), 6 deletions(-) diff --git a/src/frontend/src/pages/Index/Settings/AccountSettings/SecurityContent.tsx b/src/frontend/src/pages/Index/Settings/AccountSettings/SecurityContent.tsx index a17d55c091ed..13719bfc604e 100644 --- a/src/frontend/src/pages/Index/Settings/AccountSettings/SecurityContent.tsx +++ b/src/frontend/src/pages/Index/Settings/AccountSettings/SecurityContent.tsx @@ -81,11 +81,14 @@ function EmailSection() { data?: any ) { const vals: any = data || { email: value }; - authApi(apiUrl(ApiEndpoints.auth_email), undefined, action, vals) - .then(() => { - refetch(); - }) - .catch((res: any) => console.log(res.data)); + return authApi( + apiUrl(ApiEndpoints.auth_email), + undefined, + action, + vals + ).then(() => { + refetch(); + }); } if (isLoading) return ; @@ -171,7 +174,20 @@ function EmailSection() { From 8be0a52930a42f115453eb455daa1c9cd03ca2ad Mon Sep 17 00:00:00 2001 From: Matthias Mair Date: Mon, 20 Jan 2025 22:34:27 +0100 Subject: [PATCH 104/156] disable buttons if no email is set --- .../Settings/AccountSettings/SecurityContent.tsx | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/src/frontend/src/pages/Index/Settings/AccountSettings/SecurityContent.tsx b/src/frontend/src/pages/Index/Settings/AccountSettings/SecurityContent.tsx index 13719bfc604e..a16375f15ccf 100644 --- a/src/frontend/src/pages/Index/Settings/AccountSettings/SecurityContent.tsx +++ b/src/frontend/src/pages/Index/Settings/AccountSettings/SecurityContent.tsx @@ -75,6 +75,9 @@ function EmailSection() { queryFn: () => authApi(apiUrl(ApiEndpoints.auth_email)).then((res) => res.data.data) }); + const emailAvailable = useMemo(() => { + return data == undefined || data.length == 0; + }, [data]); function runServerAction( action: 'post' | 'put' | 'delete' = 'post', @@ -96,7 +99,7 @@ function EmailSection() { return ( - {data.length == 0 ? ( + {emailAvailable ? ( } title={t`Not configured`} @@ -161,13 +164,20 @@ function EmailSection() { onClick={() => runServerAction('post', { email: value, primary: true }) } + disabled={emailAvailable} > Make Primary - - From aaa02190597f7975fe67bce4ae09e5e370aaeb0d Mon Sep 17 00:00:00 2001 From: Matthias Mair Date: Mon, 20 Jan 2025 23:51:39 +0100 Subject: [PATCH 105/156] add custom adapters for MFA and headless authentication to use upstreamed features --- .../InvenTree/InvenTree/auth_overrides.py | 38 ++++++++++++++++++- src/backend/InvenTree/InvenTree/settings.py | 4 +- 2 files changed, 40 insertions(+), 2 deletions(-) diff --git a/src/backend/InvenTree/InvenTree/auth_overrides.py b/src/backend/InvenTree/InvenTree/auth_overrides.py index 95aa26c23184..569f5fc74d2e 100644 --- a/src/backend/InvenTree/InvenTree/auth_overrides.py +++ b/src/backend/InvenTree/InvenTree/auth_overrides.py @@ -3,14 +3,16 @@ from django import forms from django.conf import settings from django.contrib.auth.models import Group -from django.http import HttpResponseRedirect +from django.http import HttpRequest, HttpResponseRedirect from django.urls import reverse from django.utils.translation import gettext_lazy as _ import structlog from allauth.account.adapter import DefaultAccountAdapter from allauth.account.forms import LoginForm, SignupForm, set_form_field_order +from allauth.headless.adapter import DefaultHeadlessAdapter from allauth.headless.tokens.sessions import SessionTokenStrategy +from allauth.mfa.adapter import DefaultMFAAdapter from allauth.socialaccount.adapter import DefaultSocialAccountAdapter import InvenTree.helpers_model @@ -210,6 +212,40 @@ def authentication_error( logger.error("SSO error for provider '%s' - check admin error log", provider_id) +class CustomMFAAdapter(DefaultMFAAdapter): + """Override of adapter to use dynamic settings.""" + + def block_email_registering(self, user) -> bool: + """Statically disable email registration blocking.""" + return False + + +class CustomHeadlessAdapter(DefaultHeadlessAdapter): + """Override of adapter to use dynamic settings.""" + + def get_frontend_url(self, request: HttpRequest, urlname, **kwargs): + """Get the frontend URL for the given URL name respecting the request.""" + HEADLESS_FRONTEND_URLS = { + 'account_confirm_email': ['verify-email/', '{key}'], + 'account_reset_password': 'reset-password', + 'account_reset_password_from_key': ['set-password?key=', '{key}'], + 'account_signup': 'register', + 'socialaccount_login_error': 'social-login-error', + } + if urlname not in HEADLESS_FRONTEND_URLS: + raise ValueError( + f'URL name "{urlname}" not found in HEADLESS_FRONTEND_URLS' + ) + + url = HEADLESS_FRONTEND_URLS[urlname] + if isinstance(url, list): + return ( + request.build_absolute_uri(f'/{settings.FRONTEND_URL_BASE}/{url[0]}') + + url[1] + ) + return request.build_absolute_uri(f'/{settings.FRONTEND_URL_BASE}/{url}') + + class DRFTokenStrategy(SessionTokenStrategy): """Strategy that InvenTrees own included Token model.""" diff --git a/src/backend/InvenTree/InvenTree/settings.py b/src/backend/InvenTree/InvenTree/settings.py index b10d3bdff1c5..938183514888 100644 --- a/src/backend/InvenTree/InvenTree/settings.py +++ b/src/backend/InvenTree/InvenTree/settings.py @@ -1300,6 +1300,8 @@ SOCIALACCOUNT_ADAPTER = 'InvenTree.auth_overrides.CustomSocialAccountAdapter' ACCOUNT_ADAPTER = 'InvenTree.auth_overrides.CustomAccountAdapter' +MFA_ADAPTER = 'InvenTree.auth_overrides.CustomMFAAdapter' +HEADLESS_ADAPTER = 'InvenTree.auth_overrides.CustomHeadlessAdapter' ACCOUNT_LOGOUT_ON_PASSWORD_CHANGE = True @@ -1307,7 +1309,7 @@ def get_frontend_url(pui_path: str): """Generate frontend url. - #TODO This function should be moved to the adapter once https://codeberg.org/allauth/django-allauth/issues/4226 is resolved. + #TODO remove this https://codeberg.org/allauth/django-allauth/issues/4226 is merged. """ host: str = 'http://localhost:8000' if not host.endswith('/'): From 5746364351c407e63860e5603016f47fd520a3cd Mon Sep 17 00:00:00 2001 From: Matthias Mair Date: Tue, 21 Jan 2025 00:09:33 +0100 Subject: [PATCH 106/156] move auth settings to status --- src/backend/InvenTree/InvenTree/api.py | 18 ++++++++++++++++++ src/frontend/src/defaults/defaults.tsx | 3 ++- src/frontend/src/states/ApiState.tsx | 18 ++++++++++++------ src/frontend/src/states/states.tsx | 5 +++++ 4 files changed, 37 insertions(+), 7 deletions(-) diff --git a/src/backend/InvenTree/InvenTree/api.py b/src/backend/InvenTree/InvenTree/api.py index 04ecbad05b64..cf503689484e 100644 --- a/src/backend/InvenTree/InvenTree/api.py +++ b/src/backend/InvenTree/InvenTree/api.py @@ -20,7 +20,10 @@ import InvenTree.version import users.models +from common.settings import get_global_setting +from InvenTree.auth_overrides import registration_enabled from InvenTree.mixins import ListCreateAPI +from InvenTree.sso import sso_registration_enabled from InvenTree.templatetags.inventree_extras import plugins_info from part.models import Part from plugin.serializers import MetadataSerializer @@ -198,6 +201,13 @@ def list(self, request, *args, **kwargs): class InfoApiSerializer(serializers.Serializer): """InvenTree server information - some information might be blanked if called without elevated credentials.""" + class SettingsSerializer(serializers.Serializer): + """Serializer for InfoApiSerializer.""" + + sso_registration = serializers.BooleanField() + registration_enabled = serializers.BooleanField() + password_forgotten_enabled = serializers.BooleanField() + server = serializers.CharField(read_only=True) version = serializers.CharField(read_only=True) instance = serializers.CharField(read_only=True) @@ -220,6 +230,7 @@ class InfoApiSerializer(serializers.Serializer): installer = serializers.CharField(read_only=True) target = serializers.CharField(read_only=True) django_admin = serializers.CharField(read_only=True) + settings = SettingsSerializer(read_only=True, many=False) class InfoView(APIView): @@ -272,6 +283,13 @@ def get(self, request, *args, **kwargs): 'django_admin': settings.INVENTREE_ADMIN_URL if (is_staff and settings.INVENTREE_ADMIN_ENABLED) else None, + 'settings': { + 'sso_registration': sso_registration_enabled(), + 'registration_enabled': registration_enabled(), + 'password_forgotten_enabled': get_global_setting( + 'LOGIN_ENABLE_PWD_FORGOT' + ), + }, } return JsonResponse(data) diff --git a/src/frontend/src/defaults/defaults.tsx b/src/frontend/src/defaults/defaults.tsx index 14227fb31e43..e70c55be5235 100644 --- a/src/frontend/src/defaults/defaults.tsx +++ b/src/frontend/src/defaults/defaults.tsx @@ -19,7 +19,8 @@ export const emptyServerAPI = { installer: null, target: null, default_locale: null, - django_admin: null + django_admin: null, + settings: null }; export interface SiteMarkProps { diff --git a/src/frontend/src/states/ApiState.tsx b/src/frontend/src/states/ApiState.tsx index 0df04318c660..bcbdd6c8e3aa 100644 --- a/src/frontend/src/states/ApiState.tsx +++ b/src/frontend/src/states/ApiState.tsx @@ -19,6 +19,13 @@ interface ServerApiStateProps { password_forgotten_enabled: () => boolean; } +function get_server_setting(val: any) { + if (val === null || val === undefined) { + return false; + } + return val; +} + export const useServerApiState = create()( persist( (set, get) => ({ @@ -58,16 +65,15 @@ export const useServerApiState = create()( return !(data === undefined || data.length == 0); }, registration_enabled: () => { - // TODO - return false; + return get_server_setting(get().server?.settings?.registration_enabled); }, sso_registration_enabled: () => { - // TODO - return false; + return get_server_setting(get().server?.settings?.sso_registration); }, password_forgotten_enabled: () => { - // TODO - return false; + return get_server_setting( + get().server?.settings?.password_forgotten_enabled + ); } }), { diff --git a/src/frontend/src/states/states.tsx b/src/frontend/src/states/states.tsx index ea632d95a5a1..fa24b578f364 100644 --- a/src/frontend/src/states/states.tsx +++ b/src/frontend/src/states/states.tsx @@ -48,6 +48,11 @@ export interface ServerAPIProps { target: null | string; default_locale: null | string; django_admin: null | string; + settings: { + sso_registration: null | boolean; + registration_enabled: null | boolean; + password_forgotten_enabled: null | boolean; + } | null; } export interface AuthContext { From 254ab4ef8f2b2747195fef1552c09a5684275adc Mon Sep 17 00:00:00 2001 From: Matthias Mair Date: Tue, 21 Jan 2025 00:45:54 +0100 Subject: [PATCH 107/156] respect more settings --- src/backend/InvenTree/InvenTree/auth_overrides.py | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/backend/InvenTree/InvenTree/auth_overrides.py b/src/backend/InvenTree/InvenTree/auth_overrides.py index 569f5fc74d2e..20795f26e605 100644 --- a/src/backend/InvenTree/InvenTree/auth_overrides.py +++ b/src/backend/InvenTree/InvenTree/auth_overrides.py @@ -3,6 +3,7 @@ from django import forms from django.conf import settings from django.contrib.auth.models import Group +from django.core.exceptions import PermissionDenied from django.http import HttpRequest, HttpResponseRedirect from django.urls import reverse from django.utils.translation import gettext_lazy as _ @@ -185,6 +186,12 @@ def get_email_confirmation_url(self, request, emailconfirmation): url = InvenTree.helpers_model.construct_absolute_url(url) return url + def send_password_reset_mail(self, user, email, context): + """Send the password reset mail.""" + if not get_global_setting('LOGIN_ENABLE_PWD_FORGOT'): + raise PermissionDenied('Password reset is disabled') + return super().send_password_reset_mail(self, user, email, context) + class CustomSocialAccountAdapter(RegistrationMixin, DefaultSocialAccountAdapter): """Override of adapter to use dynamic settings.""" @@ -211,6 +218,10 @@ def authentication_error( log_error(path, error_name=error, error_data=exception) logger.error("SSO error for provider '%s' - check admin error log", provider_id) + def get_connect_redirect_url(self, request, socialaccount): + """Redirect to the frontend after connecting an account.""" + return request.build_absolute_uri(f'/{settings.FRONTEND_URL_BASE}/') + class CustomMFAAdapter(DefaultMFAAdapter): """Override of adapter to use dynamic settings.""" From e9a47b2a8ba6f01bb6043571246ea0ea3ec8aa4c Mon Sep 17 00:00:00 2001 From: Matthias Mair Date: Tue, 21 Jan 2025 00:48:56 +0100 Subject: [PATCH 108/156] update settings --- src/backend/InvenTree/InvenTree/settings.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/backend/InvenTree/InvenTree/settings.py b/src/backend/InvenTree/InvenTree/settings.py index 938183514888..01393c10521c 100644 --- a/src/backend/InvenTree/InvenTree/settings.py +++ b/src/backend/InvenTree/InvenTree/settings.py @@ -1253,6 +1253,8 @@ ACCOUNT_EMAIL_CONFIRMATION_EXPIRE_DAYS = get_setting( 'INVENTREE_LOGIN_CONFIRM_DAYS', 'login_confirm_days', 3, typecast=int ) +ACCOUNT_EMAIL_UNKNOWN_ACCOUNTS = False +ACCOUNT_EMAIL_NOTIFICATIONS = True USERSESSIONS_TRACK_ACTIVITY = True # allauth rate limiting: https://docs.allauth.org/en/latest/account/rate_limits.html From 7c4d0e8ff642724a2392b11788474bef21637bba Mon Sep 17 00:00:00 2001 From: Matthias Mair Date: Tue, 21 Jan 2025 00:54:24 +0100 Subject: [PATCH 109/156] bump api version --- src/backend/InvenTree/InvenTree/api_version.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/backend/InvenTree/InvenTree/api_version.py b/src/backend/InvenTree/InvenTree/api_version.py index 82a12c135024..7d1eccf75b12 100644 --- a/src/backend/InvenTree/InvenTree/api_version.py +++ b/src/backend/InvenTree/InvenTree/api_version.py @@ -1,13 +1,13 @@ """InvenTree API version information.""" # InvenTree API version -INVENTREE_API_VERSION = 303 +INVENTREE_API_VERSION = 304 """Increment this API version number whenever there is a significant change to the API that any clients need to know about.""" INVENTREE_API_TEXT = """ -v303 - 2025-01-20 : https://github.com/inventree/InvenTree/pull/6293 +v304 - 2025-01-25 : https://github.com/inventree/InvenTree/pull/6293 - Removes a considerable amount of old auth endpoints - Introduces allauth based REST API From eadf7acf0e00fbeb4b1f713014fd40b97cc4e9e8 Mon Sep 17 00:00:00 2001 From: Matthias Mair Date: Tue, 21 Jan 2025 02:50:59 +0100 Subject: [PATCH 110/156] remove depreceated docs part --- docs/docs/api/schema.md | 1 - docs/docs/api/schema/auth.md | 7 ------- 2 files changed, 8 deletions(-) delete mode 100644 docs/docs/api/schema/auth.md diff --git a/docs/docs/api/schema.md b/docs/docs/api/schema.md index 441e977e2e9a..8b686b403646 100644 --- a/docs/docs/api/schema.md +++ b/docs/docs/api/schema.md @@ -22,7 +22,6 @@ API schema documentation is split into the following categories: | Category | Description | | --- | --- | -| [Authorization and Authentication](./schema/auth.md) | Authorization and Authentication | | [Background Task Management](./schema/background-task.md) | Background Task Management | | [Barcode Scanning](./schema/barcode.md) | Barcode Scanning | | [Bill of Materials](./schema/bom.md) | Bill of Materials | diff --git a/docs/docs/api/schema/auth.md b/docs/docs/api/schema/auth.md deleted file mode 100644 index 62c8b50103ed..000000000000 --- a/docs/docs/api/schema/auth.md +++ /dev/null @@ -1,7 +0,0 @@ ---- -title: Authorization and Authentication ---- - -The *Authorization and Authentication* section of the InvenTree API schema is documented below. - -[OAD(./docs/docs/api/schema/auth.yml)] From dd32ca337d5957599a5076071bcd9cf0d40b6af2 Mon Sep 17 00:00:00 2001 From: Matthias Mair Date: Tue, 21 Jan 2025 03:12:02 +0100 Subject: [PATCH 111/156] remove dj_rest_auth stuff --- .../InvenTree/auth_override_views.py | 40 ------------------- src/backend/InvenTree/InvenTree/settings.py | 3 -- 2 files changed, 43 deletions(-) delete mode 100644 src/backend/InvenTree/InvenTree/auth_override_views.py diff --git a/src/backend/InvenTree/InvenTree/auth_override_views.py b/src/backend/InvenTree/InvenTree/auth_override_views.py deleted file mode 100644 index 3362c3bf07de..000000000000 --- a/src/backend/InvenTree/InvenTree/auth_override_views.py +++ /dev/null @@ -1,40 +0,0 @@ -"""Overrides for registration view.""" - -from django.utils.translation import gettext_lazy as _ - -from allauth.account import app_settings as allauth_account_settings -from dj_rest_auth.app_settings import api_settings -from dj_rest_auth.registration.views import RegisterView - - -class CustomRegisterView(RegisterView): - """Registers a new user. - - Accepts the following POST parameters: username, email, password1, password2. - """ - - # Fixes https://github.com/inventree/InvenTree/issues/8707 - # This contains code from dj-rest-auth 7.0 - therefore the version was pinned - def get_response_data(self, user): - """Override to fix check for auth_model.""" - if ( - allauth_account_settings.EMAIL_VERIFICATION - == allauth_account_settings.EmailVerificationMethod.MANDATORY - ): - return {'detail': _('Verification e-mail sent.')} - - if api_settings.USE_JWT: - data = { - 'user': user, - 'access': self.access_token, - 'refresh': self.refresh_token, - } - return api_settings.JWT_SERIALIZER( - data, context=self.get_serializer_context() - ).data - elif self.token_model: - # Only change in this block is below - return api_settings.TOKEN_SERIALIZER( - user.api_tokens.last(), context=self.get_serializer_context() - ).data - return None diff --git a/src/backend/InvenTree/InvenTree/settings.py b/src/backend/InvenTree/InvenTree/settings.py index 01393c10521c..9f725ac07694 100644 --- a/src/backend/InvenTree/InvenTree/settings.py +++ b/src/backend/InvenTree/InvenTree/settings.py @@ -558,9 +558,6 @@ if USE_JWT: JWT_AUTH_COOKIE = 'inventree-auth' JWT_AUTH_REFRESH_COOKIE = 'inventree-token' - REST_FRAMEWORK['DEFAULT_AUTHENTICATION_CLASSES'].append( - 'dj_rest_auth.jwt_auth.JWTCookieAuthentication' - ) INSTALLED_APPS.append('rest_framework_simplejwt') # WSGI default setting From 2110debfdf7fe846e5854fc502955b2f3d29cbe8 Mon Sep 17 00:00:00 2001 From: Matthias Mair Date: Wed, 22 Jan 2025 20:08:47 +0100 Subject: [PATCH 112/156] fix api_version bump --- src/backend/InvenTree/InvenTree/api_version.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/backend/InvenTree/InvenTree/api_version.py b/src/backend/InvenTree/InvenTree/api_version.py index ebfdca7691ac..d2c1ef927546 100644 --- a/src/backend/InvenTree/InvenTree/api_version.py +++ b/src/backend/InvenTree/InvenTree/api_version.py @@ -1,13 +1,13 @@ """InvenTree API version information.""" # InvenTree API version -INVENTREE_API_VERSION = 304 +INVENTREE_API_VERSION = 305 """Increment this API version number whenever there is a significant change to the API that any clients need to know about.""" INVENTREE_API_TEXT = """ -v304 - 2025-01-25 : https://github.com/inventree/InvenTree/pull/6293 +v305 - 2025-01-25 : https://github.com/inventree/InvenTree/pull/6293 - Removes a considerable amount of old auth endpoints - Introduces allauth based REST API From dad283621fd65ccffc3d318a8d406fdc433731d6 Mon Sep 17 00:00:00 2001 From: Matthias Mair Date: Sun, 26 Jan 2025 15:04:58 +0100 Subject: [PATCH 113/156] remove temp fix --- src/backend/InvenTree/InvenTree/settings.py | 22 --------------------- src/backend/requirements.in | 2 +- src/backend/requirements.txt | 3 +-- 3 files changed, 2 insertions(+), 25 deletions(-) diff --git a/src/backend/InvenTree/InvenTree/settings.py b/src/backend/InvenTree/InvenTree/settings.py index 9f725ac07694..e00adc7d59e0 100644 --- a/src/backend/InvenTree/InvenTree/settings.py +++ b/src/backend/InvenTree/InvenTree/settings.py @@ -1303,28 +1303,6 @@ HEADLESS_ADAPTER = 'InvenTree.auth_overrides.CustomHeadlessAdapter' ACCOUNT_LOGOUT_ON_PASSWORD_CHANGE = True - -# region to_be_moved -def get_frontend_url(pui_path: str): - """Generate frontend url. - - #TODO remove this https://codeberg.org/allauth/django-allauth/issues/4226 is merged. - """ - host: str = 'http://localhost:8000' - if not host.endswith('/'): - host += '/' - return f'{host}{FRONTEND_URL_BASE}/{pui_path}' - - -HEADLESS_FRONTEND_URLS = { - 'account_confirm_email': get_frontend_url('verify-email/{key}'), # noqa: RUF027 - 'account_reset_password': get_frontend_url('reset-password'), - 'account_reset_password_from_key': get_frontend_url('set-password?key={key}'), # noqa: RUF027 - 'account_signup': get_frontend_url('register'), - 'socialaccount_login_error': get_frontend_url('social-login-error'), -} -# endregion to_be_moved - HEADLESS_ONLY = True HEADLESS_TOKEN_STRATEGY = 'InvenTree.auth_overrides.DRFTokenStrategy' MFA_ENABLED = get_boolean_setting( diff --git a/src/backend/requirements.in b/src/backend/requirements.in index 81871c774934..3528c028bd6f 100644 --- a/src/backend/requirements.in +++ b/src/backend/requirements.in @@ -2,7 +2,7 @@ Django<5.0 # Django package coreapi # API documentation for djangorestframework cryptography>=40.0.0,!=40.0.2,<=43.0.3 # Core cryptographic functionality -django-allauth[mfa,socialaccount,saml,openid] # SSO for external providers via OpenID +django-allauth[mfa,socialaccount,saml,openid] @ git+https://codeberg.org/allauth/django-allauth@main # SSO for external providers via OpenID django-cleanup # Automated deletion of old / unused uploaded files django-cors-headers # CORS headers extension for DRF django-dbbackup # Backup / restore of database and media files diff --git a/src/backend/requirements.txt b/src/backend/requirements.txt index 2645e137d9e0..71809d767813 100644 --- a/src/backend/requirements.txt +++ b/src/backend/requirements.txt @@ -412,8 +412,7 @@ django==4.2.18 \ # djangorestframework # djangorestframework-simplejwt # drf-spectacular -django-allauth[mfa, openid, saml, socialaccount]==65.3.1 \ - --hash=sha256:e02e951b71a2753a746459f2efa114c7c72bf2cef6887dbe8607a577c0350587 +django-allauth[mfa, openid, saml, socialaccount] @ git+https://codeberg.org/allauth/django-allauth@b802a0ff6206a8b7b6c9ecf7148d414f6868d47a # via -r src/backend/requirements.in django-cleanup==9.0.0 \ --hash=sha256:19f8b0e830233f9f0f683b17181f414672a0f48afe3ea3cc80ba47ae40ad880c \ From d91363e413d8ecc3f4c79fd5ac35e5fd4351d8cc Mon Sep 17 00:00:00 2001 From: Matthias Mair Date: Sun, 26 Jan 2025 15:30:40 +0100 Subject: [PATCH 114/156] fix provider login --- src/frontend/src/functions/auth.tsx | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/src/frontend/src/functions/auth.tsx b/src/frontend/src/functions/auth.tsx index 175ecc14b838..39e45b4757de 100644 --- a/src/frontend/src/functions/auth.tsx +++ b/src/frontend/src/functions/auth.tsx @@ -10,6 +10,7 @@ import { useLocalState } from '../states/LocalState'; import { useUserState } from '../states/UserState'; import { type Provider, fetchGlobalStates } from '../states/states'; import { showLoginNotification } from './notifications'; +import { generateUrl } from './urls'; export function followRedirect(navigate: NavigateFunction, redirect: any) { let url = redirect?.redirectUrl ?? '/home'; @@ -287,21 +288,17 @@ export function clearCsrfCookie() { 'csrftoken=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/;'; } -export function ProviderLogin( +export async function ProviderLogin( provider: Provider, process: 'login' | 'connect' = 'login' ) { - const { host } = useLocalState.getState(); - // TODO - const loc = window.location; - const values = { + await ensureCsrf(); + post(generateUrl(apiUrl(ApiEndpoints.auth_provider_redirect)), { provider: provider.id, - callback_url: 'http://localhost:8000/logged-in', + callback_url: generateUrl('/logged-in'), process: process, csrfmiddlewaretoken: getCsrfCookie() - }; - const url = `${host}${apiUrl(ApiEndpoints.auth_provider_redirect)}`; - post(url, values); + }); } /** From 4fb010864b9a02d70bf35b9411ccb9bd169e467c Mon Sep 17 00:00:00 2001 From: Matthias Mair Date: Sun, 26 Jan 2025 15:39:19 +0100 Subject: [PATCH 115/156] remove unsupported option --- src/backend/InvenTree/InvenTree/auth_overrides.py | 9 --------- src/backend/InvenTree/InvenTree/settings.py | 1 - 2 files changed, 10 deletions(-) diff --git a/src/backend/InvenTree/InvenTree/auth_overrides.py b/src/backend/InvenTree/InvenTree/auth_overrides.py index 20795f26e605..40394297093d 100644 --- a/src/backend/InvenTree/InvenTree/auth_overrides.py +++ b/src/backend/InvenTree/InvenTree/auth_overrides.py @@ -13,7 +13,6 @@ from allauth.account.forms import LoginForm, SignupForm, set_form_field_order from allauth.headless.adapter import DefaultHeadlessAdapter from allauth.headless.tokens.sessions import SessionTokenStrategy -from allauth.mfa.adapter import DefaultMFAAdapter from allauth.socialaccount.adapter import DefaultSocialAccountAdapter import InvenTree.helpers_model @@ -223,14 +222,6 @@ def get_connect_redirect_url(self, request, socialaccount): return request.build_absolute_uri(f'/{settings.FRONTEND_URL_BASE}/') -class CustomMFAAdapter(DefaultMFAAdapter): - """Override of adapter to use dynamic settings.""" - - def block_email_registering(self, user) -> bool: - """Statically disable email registration blocking.""" - return False - - class CustomHeadlessAdapter(DefaultHeadlessAdapter): """Override of adapter to use dynamic settings.""" diff --git a/src/backend/InvenTree/InvenTree/settings.py b/src/backend/InvenTree/InvenTree/settings.py index e00adc7d59e0..12ad622f0acc 100644 --- a/src/backend/InvenTree/InvenTree/settings.py +++ b/src/backend/InvenTree/InvenTree/settings.py @@ -1299,7 +1299,6 @@ SOCIALACCOUNT_ADAPTER = 'InvenTree.auth_overrides.CustomSocialAccountAdapter' ACCOUNT_ADAPTER = 'InvenTree.auth_overrides.CustomAccountAdapter' -MFA_ADAPTER = 'InvenTree.auth_overrides.CustomMFAAdapter' HEADLESS_ADAPTER = 'InvenTree.auth_overrides.CustomHeadlessAdapter' ACCOUNT_LOGOUT_ON_PASSWORD_CHANGE = True From 00bb6c5274ee673948280ec084831edfa40ec3de Mon Sep 17 00:00:00 2001 From: Matthias Mair Date: Sun, 26 Jan 2025 15:48:25 +0100 Subject: [PATCH 116/156] remove hash requirement for now --- .pre-commit-config.yaml | 2 +- pyproject.toml | 1 - src/backend/requirements.txt | 1738 +++------------------------------- tasks.py | 4 +- 4 files changed, 142 insertions(+), 1603 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index af4fab920c9b..999ae6e0da82 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -36,7 +36,7 @@ repos: files: src/backend/requirements-dev\.(in|txt)$ - id: pip-compile name: pip-compile requirements.txt - args: [src/backend/requirements.in, -o, src/backend/requirements.txt, --no-strip-extras, --generate-hashes] + args: [src/backend/requirements.in, -o, src/backend/requirements.txt, --no-strip-extras] files: src/backend/requirements\.(in|txt)$ - id: pip-compile name: pip-compile requirements.txt diff --git a/pyproject.toml b/pyproject.toml index d596e0f0b60b..32ab9e0d4392 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -91,7 +91,6 @@ line-ending = "auto" [tool.uv.pip] python-version = "3.9" no-strip-extras=true -generate-hashes=true [tool.coverage.run] source = ["src/backend/InvenTree", "InvenTree"] diff --git a/src/backend/requirements.txt b/src/backend/requirements.txt index 71809d767813..52fa282af414 100644 --- a/src/backend/requirements.txt +++ b/src/backend/requirements.txt @@ -1,389 +1,54 @@ # This file was autogenerated by uv via the following command: -# uv pip compile src/backend/requirements.in -o src/backend/requirements.txt --no-strip-extras --generate-hashes -asgiref==3.8.1 \ - --hash=sha256:3e1e3ecc849832fe52ccf2cb6686b7a55f82bb1d6aee72a58826471390335e47 \ - --hash=sha256:c343bd80a0bec947a9860adb4c432ffa7db769836c64238fc34bdc3fec84d590 +# uv pip compile src/backend/requirements.in -o src/backend/requirements.txt --no-strip-extras +asgiref==3.8.1 # via # django # django-allauth # django-cors-headers # django-structlog -async-timeout==5.0.1 \ - --hash=sha256:39e3809566ff85354557ec2398b55e096c8364bacac9405a7a1fa429e77fe76c \ - --hash=sha256:d9321a7a3d5a6a5e187e824d2fa0793ce379a202935782d555d6e9d2735677d3 +async-timeout==5.0.1 # via redis -attrs==24.3.0 \ - --hash=sha256:8f5c07333d543103541ba7be0e2ce16eeee8130cb0b3f9238ab904ce1e85baff \ - --hash=sha256:ac96cd038792094f438ad1f6ff80837353805ac950cd2aa0e0625ef19850c308 +attrs==24.3.0 # via # jsonschema # referencing -babel==2.16.0 \ - --hash=sha256:368b5b98b37c06b7daf6696391c3240c938b37767d4584413e8438c5c435fa8b \ - --hash=sha256:d1f3554ca26605fe173f3de0c65f750f5a42f924499bf134de6423582298e316 +babel==2.16.0 # via py-moneyed -bleach[css]==6.2.0 \ - --hash=sha256:117d9c6097a7c3d22fd578fcd8d35ff1e125df6736f554da4e432fdd63f31e5e \ - --hash=sha256:123e894118b8a599fd80d3ec1a6d4cc7ce4e5882b1317a7e1ba69b56e95f991f +bleach[css]==6.2.0 # via django-markdownify -brotli==1.1.0 \ - --hash=sha256:03d20af184290887bdea3f0f78c4f737d126c74dc2f3ccadf07e54ceca3bf208 \ - --hash=sha256:0541e747cce78e24ea12d69176f6a7ddb690e62c425e01d31cc065e69ce55b48 \ - --hash=sha256:069a121ac97412d1fe506da790b3e69f52254b9df4eb665cd42460c837193354 \ - --hash=sha256:0737ddb3068957cf1b054899b0883830bb1fec522ec76b1098f9b6e0f02d9419 \ - --hash=sha256:0b63b949ff929fbc2d6d3ce0e924c9b93c9785d877a21a1b678877ffbbc4423a \ - --hash=sha256:0c6244521dda65ea562d5a69b9a26120769b7a9fb3db2fe9545935ed6735b128 \ - --hash=sha256:11d00ed0a83fa22d29bc6b64ef636c4552ebafcef57154b4ddd132f5638fbd1c \ - --hash=sha256:141bd4d93984070e097521ed07e2575b46f817d08f9fa42b16b9b5f27b5ac088 \ - --hash=sha256:19c116e796420b0cee3da1ccec3b764ed2952ccfcc298b55a10e5610ad7885f9 \ - --hash=sha256:1ab4fbee0b2d9098c74f3057b2bc055a8bd92ccf02f65944a241b4349229185a \ - --hash=sha256:1ae56aca0402a0f9a3431cddda62ad71666ca9d4dc3a10a142b9dce2e3c0cda3 \ - --hash=sha256:1b2c248cd517c222d89e74669a4adfa5577e06ab68771a529060cf5a156e9757 \ - --hash=sha256:1e9a65b5736232e7a7f91ff3d02277f11d339bf34099a56cdab6a8b3410a02b2 \ - --hash=sha256:224e57f6eac61cc449f498cc5f0e1725ba2071a3d4f48d5d9dffba42db196438 \ - --hash=sha256:22fc2a8549ffe699bfba2256ab2ed0421a7b8fadff114a3d201794e45a9ff578 \ - --hash=sha256:23032ae55523cc7bccb4f6a0bf368cd25ad9bcdcc1990b64a647e7bbcce9cb5b \ - --hash=sha256:2333e30a5e00fe0fe55903c8832e08ee9c3b1382aacf4db26664a16528d51b4b \ - --hash=sha256:2954c1c23f81c2eaf0b0717d9380bd348578a94161a65b3a2afc62c86467dd68 \ - --hash=sha256:2a24c50840d89ded6c9a8fdc7b6ed3692ed4e86f1c4a4a938e1e92def92933e0 \ - --hash=sha256:2de9d02f5bda03d27ede52e8cfe7b865b066fa49258cbab568720aa5be80a47d \ - --hash=sha256:2feb1d960f760a575dbc5ab3b1c00504b24caaf6986e2dc2b01c09c87866a943 \ - --hash=sha256:30924eb4c57903d5a7526b08ef4a584acc22ab1ffa085faceb521521d2de32dd \ - --hash=sha256:316cc9b17edf613ac76b1f1f305d2a748f1b976b033b049a6ecdfd5612c70409 \ - --hash=sha256:32d95b80260d79926f5fab3c41701dbb818fde1c9da590e77e571eefd14abe28 \ - --hash=sha256:38025d9f30cf4634f8309c6874ef871b841eb3c347e90b0851f63d1ded5212da \ - --hash=sha256:39da8adedf6942d76dc3e46653e52df937a3c4d6d18fdc94a7c29d263b1f5b50 \ - --hash=sha256:3c0ef38c7a7014ffac184db9e04debe495d317cc9c6fb10071f7fefd93100a4f \ - --hash=sha256:3d7954194c36e304e1523f55d7042c59dc53ec20dd4e9ea9d151f1b62b4415c0 \ - --hash=sha256:3ee8a80d67a4334482d9712b8e83ca6b1d9bc7e351931252ebef5d8f7335a547 \ - --hash=sha256:4093c631e96fdd49e0377a9c167bfd75b6d0bad2ace734c6eb20b348bc3ea180 \ - --hash=sha256:43395e90523f9c23a3d5bdf004733246fba087f2948f87ab28015f12359ca6a0 \ - --hash=sha256:43ce1b9935bfa1ede40028054d7f48b5469cd02733a365eec8a329ffd342915d \ - --hash=sha256:4410f84b33374409552ac9b6903507cdb31cd30d2501fc5ca13d18f73548444a \ - --hash=sha256:494994f807ba0b92092a163a0a283961369a65f6cbe01e8891132b7a320e61eb \ - --hash=sha256:4d4a848d1837973bf0f4b5e54e3bec977d99be36a7895c61abb659301b02c112 \ - --hash=sha256:4ed11165dd45ce798d99a136808a794a748d5dc38511303239d4e2363c0695dc \ - --hash=sha256:4f3607b129417e111e30637af1b56f24f7a49e64763253bbc275c75fa887d4b2 \ - --hash=sha256:510b5b1bfbe20e1a7b3baf5fed9e9451873559a976c1a78eebaa3b86c57b4265 \ - --hash=sha256:524f35912131cc2cabb00edfd8d573b07f2d9f21fa824bd3fb19725a9cf06327 \ - --hash=sha256:587ca6d3cef6e4e868102672d3bd9dc9698c309ba56d41c2b9c85bbb903cdb95 \ - --hash=sha256:58d4b711689366d4a03ac7957ab8c28890415e267f9b6589969e74b6e42225ec \ - --hash=sha256:5b3cc074004d968722f51e550b41a27be656ec48f8afaeeb45ebf65b561481dd \ - --hash=sha256:5dab0844f2cf82be357a0eb11a9087f70c5430b2c241493fc122bb6f2bb0917c \ - --hash=sha256:5e55da2c8724191e5b557f8e18943b1b4839b8efc3ef60d65985bcf6f587dd38 \ - --hash=sha256:5eeb539606f18a0b232d4ba45adccde4125592f3f636a6182b4a8a436548b914 \ - --hash=sha256:5f4d5ea15c9382135076d2fb28dde923352fe02951e66935a9efaac8f10e81b0 \ - --hash=sha256:5fb2ce4b8045c78ebbc7b8f3c15062e435d47e7393cc57c25115cfd49883747a \ - --hash=sha256:6172447e1b368dcbc458925e5ddaf9113477b0ed542df258d84fa28fc45ceea7 \ - --hash=sha256:6967ced6730aed543b8673008b5a391c3b1076d834ca438bbd70635c73775368 \ - --hash=sha256:6974f52a02321b36847cd19d1b8e381bf39939c21efd6ee2fc13a28b0d99348c \ - --hash=sha256:6c3020404e0b5eefd7c9485ccf8393cfb75ec38ce75586e046573c9dc29967a0 \ - --hash=sha256:6c6e0c425f22c1c719c42670d561ad682f7bfeeef918edea971a79ac5252437f \ - --hash=sha256:70051525001750221daa10907c77830bc889cb6d865cc0b813d9db7fefc21451 \ - --hash=sha256:7905193081db9bfa73b1219140b3d315831cbff0d8941f22da695832f0dd188f \ - --hash=sha256:7bc37c4d6b87fb1017ea28c9508b36bbcb0c3d18b4260fcdf08b200c74a6aee8 \ - --hash=sha256:7c4855522edb2e6ae7fdb58e07c3ba9111e7621a8956f481c68d5d979c93032e \ - --hash=sha256:7e4c4629ddad63006efa0ef968c8e4751c5868ff0b1c5c40f76524e894c50248 \ - --hash=sha256:7eedaa5d036d9336c95915035fb57422054014ebdeb6f3b42eac809928e40d0c \ - --hash=sha256:7f4bf76817c14aa98cc6697ac02f3972cb8c3da93e9ef16b9c66573a68014f91 \ - --hash=sha256:81de08ac11bcb85841e440c13611c00b67d3bf82698314928d0b676362546724 \ - --hash=sha256:832436e59afb93e1836081a20f324cb185836c617659b07b129141a8426973c7 \ - --hash=sha256:861bf317735688269936f755fa136a99d1ed526883859f86e41a5d43c61d8966 \ - --hash=sha256:87a3044c3a35055527ac75e419dfa9f4f3667a1e887ee80360589eb8c90aabb9 \ - --hash=sha256:890b5a14ce214389b2cc36ce82f3093f96f4cc730c1cffdbefff77a7c71f2a97 \ - --hash=sha256:89f4988c7203739d48c6f806f1e87a1d96e0806d44f0fba61dba81392c9e474d \ - --hash=sha256:8bf32b98b75c13ec7cf774164172683d6e7891088f6316e54425fde1efc276d5 \ - --hash=sha256:8dadd1314583ec0bf2d1379f7008ad627cd6336625d6679cf2f8e67081b83acf \ - --hash=sha256:901032ff242d479a0efa956d853d16875d42157f98951c0230f69e69f9c09bac \ - --hash=sha256:9011560a466d2eb3f5a6e4929cf4a09be405c64154e12df0dd72713f6500e32b \ - --hash=sha256:906bc3a79de8c4ae5b86d3d75a8b77e44404b0f4261714306e3ad248d8ab0951 \ - --hash=sha256:919e32f147ae93a09fe064d77d5ebf4e35502a8df75c29fb05788528e330fe74 \ - --hash=sha256:91d7cc2a76b5567591d12c01f019dd7afce6ba8cba6571187e21e2fc418ae648 \ - --hash=sha256:929811df5462e182b13920da56c6e0284af407d1de637d8e536c5cd00a7daf60 \ - --hash=sha256:949f3b7c29912693cee0afcf09acd6ebc04c57af949d9bf77d6101ebb61e388c \ - --hash=sha256:a090ca607cbb6a34b0391776f0cb48062081f5f60ddcce5d11838e67a01928d1 \ - --hash=sha256:a1fd8a29719ccce974d523580987b7f8229aeace506952fa9ce1d53a033873c8 \ - --hash=sha256:a37b8f0391212d29b3a91a799c8e4a2855e0576911cdfb2515487e30e322253d \ - --hash=sha256:a3daabb76a78f829cafc365531c972016e4aa8d5b4bf60660ad8ecee19df7ccc \ - --hash=sha256:a469274ad18dc0e4d316eefa616d1d0c2ff9da369af19fa6f3daa4f09671fd61 \ - --hash=sha256:a599669fd7c47233438a56936988a2478685e74854088ef5293802123b5b2460 \ - --hash=sha256:a743e5a28af5f70f9c080380a5f908d4d21d40e8f0e0c8901604d15cfa9ba751 \ - --hash=sha256:a77def80806c421b4b0af06f45d65a136e7ac0bdca3c09d9e2ea4e515367c7e9 \ - --hash=sha256:a7e53012d2853a07a4a79c00643832161a910674a893d296c9f1259859a289d2 \ - --hash=sha256:a93dde851926f4f2678e704fadeb39e16c35d8baebd5252c9fd94ce8ce68c4a0 \ - --hash=sha256:aac0411d20e345dc0920bdec5548e438e999ff68d77564d5e9463a7ca9d3e7b1 \ - --hash=sha256:ae15b066e5ad21366600ebec29a7ccbc86812ed267e4b28e860b8ca16a2bc474 \ - --hash=sha256:aea440a510e14e818e67bfc4027880e2fb500c2ccb20ab21c7a7c8b5b4703d75 \ - --hash=sha256:af6fa6817889314555aede9a919612b23739395ce767fe7fcbea9a80bf140fe5 \ - --hash=sha256:b760c65308ff1e462f65d69c12e4ae085cff3b332d894637f6273a12a482d09f \ - --hash=sha256:be36e3d172dc816333f33520154d708a2657ea63762ec16b62ece02ab5e4daf2 \ - --hash=sha256:c247dd99d39e0338a604f8c2b3bc7061d5c2e9e2ac7ba9cc1be5a69cb6cd832f \ - --hash=sha256:c5529b34c1c9d937168297f2c1fde7ebe9ebdd5e121297ff9c043bdb2ae3d6fb \ - --hash=sha256:c8146669223164fc87a7e3de9f81e9423c67a79d6b3447994dfb9c95da16e2d6 \ - --hash=sha256:c8fd5270e906eef71d4a8d19b7c6a43760c6abcfcc10c9101d14eb2357418de9 \ - --hash=sha256:ca63e1890ede90b2e4454f9a65135a4d387a4585ff8282bb72964fab893f2111 \ - --hash=sha256:caf9ee9a5775f3111642d33b86237b05808dafcd6268faa492250e9b78046eb2 \ - --hash=sha256:cb1dac1770878ade83f2ccdf7d25e494f05c9165f5246b46a621cc849341dc01 \ - --hash=sha256:cdad5b9014d83ca68c25d2e9444e28e967ef16e80f6b436918c700c117a85467 \ - --hash=sha256:cdbc1fc1bc0bff1cef838eafe581b55bfbffaed4ed0318b724d0b71d4d377619 \ - --hash=sha256:ceb64bbc6eac5a140ca649003756940f8d6a7c444a68af170b3187623b43bebf \ - --hash=sha256:d0c5516f0aed654134a2fc936325cc2e642f8a0e096d075209672eb321cff408 \ - --hash=sha256:d143fd47fad1db3d7c27a1b1d66162e855b5d50a89666af46e1679c496e8e579 \ - --hash=sha256:d192f0f30804e55db0d0e0a35d83a9fead0e9a359a9ed0285dbacea60cc10a84 \ - --hash=sha256:d2b35ca2c7f81d173d2fadc2f4f31e88cc5f7a39ae5b6db5513cf3383b0e0ec7 \ - --hash=sha256:d342778ef319e1026af243ed0a07c97acf3bad33b9f29e7ae6a1f68fd083e90c \ - --hash=sha256:d487f5432bf35b60ed625d7e1b448e2dc855422e87469e3f450aa5552b0eb284 \ - --hash=sha256:d7702622a8b40c49bffb46e1e3ba2e81268d5c04a34f460978c6b5517a34dd52 \ - --hash=sha256:db85ecf4e609a48f4b29055f1e144231b90edc90af7481aa731ba2d059226b1b \ - --hash=sha256:de6551e370ef19f8de1807d0a9aa2cdfdce2e85ce88b122fe9f6b2b076837e59 \ - --hash=sha256:e1140c64812cb9b06c922e77f1c26a75ec5e3f0fb2bf92cc8c58720dec276752 \ - --hash=sha256:e4fe605b917c70283db7dfe5ada75e04561479075761a0b3866c081d035b01c1 \ - --hash=sha256:e6a904cb26bfefc2f0a6f240bdf5233be78cd2488900a2f846f3c3ac8489ab80 \ - --hash=sha256:e79e6520141d792237c70bcd7a3b122d00f2613769ae0cb61c52e89fd3443839 \ - --hash=sha256:e84799f09591700a4154154cab9787452925578841a94321d5ee8fb9a9a328f0 \ - --hash=sha256:e93dfc1a1165e385cc8239fab7c036fb2cd8093728cbd85097b284d7b99249a2 \ - --hash=sha256:efa8b278894b14d6da122a72fefcebc28445f2d3f880ac59d46c90f4c13be9a3 \ - --hash=sha256:f0d8a7a6b5983c2496e364b969f0e526647a06b075d034f3297dc66f3b360c64 \ - --hash=sha256:f0db75f47be8b8abc8d9e31bc7aad0547ca26f24a54e6fd10231d623f183d089 \ - --hash=sha256:f296c40e23065d0d6650c4aefe7470d2a25fffda489bcc3eb66083f3ac9f6643 \ - --hash=sha256:f31859074d57b4639318523d6ffdca586ace54271a73ad23ad021acd807eb14b \ - --hash=sha256:f66b5337fa213f1da0d9000bc8dc0cb5b896b726eefd9c6046f699b169c41b9e \ - --hash=sha256:f733d788519c7e3e71f0855c96618720f5d3d60c3cb829d8bbb722dddce37985 \ - --hash=sha256:fce1473f3ccc4187f75b4690cfc922628aed4d3dd013d047f95a9b3919a86596 \ - --hash=sha256:fd5f17ff8f14003595ab414e45fce13d073e0762394f957182e69035c9f3d7c2 \ - --hash=sha256:fdc3ff3bfccdc6b9cc7c342c03aa2400683f0cb891d46e94b64a197910dc4064 +brotli==1.1.0 # via fonttools -certifi==2024.12.14 \ - --hash=sha256:1275f7a45be9464efc1173084eaa30f866fe2e47d389406136d332ed4967ec56 \ - --hash=sha256:b650d30f370c2b724812bee08008be0c4163b163ddaec3f2546c1caf65f191db +certifi==2024.12.14 # via # requests # sentry-sdk -cffi==1.17.1 \ - --hash=sha256:045d61c734659cc045141be4bae381a41d89b741f795af1dd018bfb532fd0df8 \ - --hash=sha256:0984a4925a435b1da406122d4d7968dd861c1385afe3b45ba82b750f229811e2 \ - --hash=sha256:0e2b1fac190ae3ebfe37b979cc1ce69c81f4e4fe5746bb401dca63a9062cdaf1 \ - --hash=sha256:0f048dcf80db46f0098ccac01132761580d28e28bc0f78ae0d58048063317e15 \ - --hash=sha256:1257bdabf294dceb59f5e70c64a3e2f462c30c7ad68092d01bbbfb1c16b1ba36 \ - --hash=sha256:1c39c6016c32bc48dd54561950ebd6836e1670f2ae46128f67cf49e789c52824 \ - --hash=sha256:1d599671f396c4723d016dbddb72fe8e0397082b0a77a4fab8028923bec050e8 \ - --hash=sha256:28b16024becceed8c6dfbc75629e27788d8a3f9030691a1dbf9821a128b22c36 \ - --hash=sha256:2bb1a08b8008b281856e5971307cc386a8e9c5b625ac297e853d36da6efe9c17 \ - --hash=sha256:30c5e0cb5ae493c04c8b42916e52ca38079f1b235c2f8ae5f4527b963c401caf \ - --hash=sha256:31000ec67d4221a71bd3f67df918b1f88f676f1c3b535a7eb473255fdc0b83fc \ - --hash=sha256:386c8bf53c502fff58903061338ce4f4950cbdcb23e2902d86c0f722b786bbe3 \ - --hash=sha256:3edc8d958eb099c634dace3c7e16560ae474aa3803a5df240542b305d14e14ed \ - --hash=sha256:45398b671ac6d70e67da8e4224a065cec6a93541bb7aebe1b198a61b58c7b702 \ - --hash=sha256:46bf43160c1a35f7ec506d254e5c890f3c03648a4dbac12d624e4490a7046cd1 \ - --hash=sha256:4ceb10419a9adf4460ea14cfd6bc43d08701f0835e979bf821052f1805850fe8 \ - --hash=sha256:51392eae71afec0d0c8fb1a53b204dbb3bcabcb3c9b807eedf3e1e6ccf2de903 \ - --hash=sha256:5da5719280082ac6bd9aa7becb3938dc9f9cbd57fac7d2871717b1feb0902ab6 \ - --hash=sha256:610faea79c43e44c71e1ec53a554553fa22321b65fae24889706c0a84d4ad86d \ - --hash=sha256:636062ea65bd0195bc012fea9321aca499c0504409f413dc88af450b57ffd03b \ - --hash=sha256:6883e737d7d9e4899a8a695e00ec36bd4e5e4f18fabe0aca0efe0a4b44cdb13e \ - --hash=sha256:6b8b4a92e1c65048ff98cfe1f735ef8f1ceb72e3d5f0c25fdb12087a23da22be \ - --hash=sha256:6f17be4345073b0a7b8ea599688f692ac3ef23ce28e5df79c04de519dbc4912c \ - --hash=sha256:706510fe141c86a69c8ddc029c7910003a17353970cff3b904ff0686a5927683 \ - --hash=sha256:72e72408cad3d5419375fc87d289076ee319835bdfa2caad331e377589aebba9 \ - --hash=sha256:733e99bc2df47476e3848417c5a4540522f234dfd4ef3ab7fafdf555b082ec0c \ - --hash=sha256:7596d6620d3fa590f677e9ee430df2958d2d6d6de2feeae5b20e82c00b76fbf8 \ - --hash=sha256:78122be759c3f8a014ce010908ae03364d00a1f81ab5c7f4a7a5120607ea56e1 \ - --hash=sha256:805b4371bf7197c329fcb3ead37e710d1bca9da5d583f5073b799d5c5bd1eee4 \ - --hash=sha256:85a950a4ac9c359340d5963966e3e0a94a676bd6245a4b55bc43949eee26a655 \ - --hash=sha256:8f2cdc858323644ab277e9bb925ad72ae0e67f69e804f4898c070998d50b1a67 \ - --hash=sha256:9755e4345d1ec879e3849e62222a18c7174d65a6a92d5b346b1863912168b595 \ - --hash=sha256:98e3969bcff97cae1b2def8ba499ea3d6f31ddfdb7635374834cf89a1a08ecf0 \ - --hash=sha256:a08d7e755f8ed21095a310a693525137cfe756ce62d066e53f502a83dc550f65 \ - --hash=sha256:a1ed2dd2972641495a3ec98445e09766f077aee98a1c896dcb4ad0d303628e41 \ - --hash=sha256:a24ed04c8ffd54b0729c07cee15a81d964e6fee0e3d4d342a27b020d22959dc6 \ - --hash=sha256:a45e3c6913c5b87b3ff120dcdc03f6131fa0065027d0ed7ee6190736a74cd401 \ - --hash=sha256:a9b15d491f3ad5d692e11f6b71f7857e7835eb677955c00cc0aefcd0669adaf6 \ - --hash=sha256:ad9413ccdeda48c5afdae7e4fa2192157e991ff761e7ab8fdd8926f40b160cc3 \ - --hash=sha256:b2ab587605f4ba0bf81dc0cb08a41bd1c0a5906bd59243d56bad7668a6fc6c16 \ - --hash=sha256:b62ce867176a75d03a665bad002af8e6d54644fad99a3c70905c543130e39d93 \ - --hash=sha256:c03e868a0b3bc35839ba98e74211ed2b05d2119be4e8a0f224fba9384f1fe02e \ - --hash=sha256:c59d6e989d07460165cc5ad3c61f9fd8f1b4796eacbd81cee78957842b834af4 \ - --hash=sha256:c7eac2ef9b63c79431bc4b25f1cd649d7f061a28808cbc6c47b534bd789ef964 \ - --hash=sha256:c9c3d058ebabb74db66e431095118094d06abf53284d9c81f27300d0e0d8bc7c \ - --hash=sha256:ca74b8dbe6e8e8263c0ffd60277de77dcee6c837a3d0881d8c1ead7268c9e576 \ - --hash=sha256:caaf0640ef5f5517f49bc275eca1406b0ffa6aa184892812030f04c2abf589a0 \ - --hash=sha256:cdf5ce3acdfd1661132f2a9c19cac174758dc2352bfe37d98aa7512c6b7178b3 \ - --hash=sha256:d016c76bdd850f3c626af19b0542c9677ba156e4ee4fccfdd7848803533ef662 \ - --hash=sha256:d01b12eeeb4427d3110de311e1774046ad344f5b1a7403101878976ecd7a10f3 \ - --hash=sha256:d63afe322132c194cf832bfec0dc69a99fb9bb6bbd550f161a49e9e855cc78ff \ - --hash=sha256:da95af8214998d77a98cc14e3a3bd00aa191526343078b530ceb0bd710fb48a5 \ - --hash=sha256:dd398dbc6773384a17fe0d3e7eeb8d1a21c2200473ee6806bb5e6a8e62bb73dd \ - --hash=sha256:de2ea4b5833625383e464549fec1bc395c1bdeeb5f25c4a3a82b5a8c756ec22f \ - --hash=sha256:de55b766c7aa2e2a3092c51e0483d700341182f08e67c63630d5b6f200bb28e5 \ - --hash=sha256:df8b1c11f177bc2313ec4b2d46baec87a5f3e71fc8b45dab2ee7cae86d9aba14 \ - --hash=sha256:e03eab0a8677fa80d646b5ddece1cbeaf556c313dcfac435ba11f107ba117b5d \ - --hash=sha256:e221cf152cff04059d011ee126477f0d9588303eb57e88923578ace7baad17f9 \ - --hash=sha256:e31ae45bc2e29f6b2abd0de1cc3b9d5205aa847cafaecb8af1476a609a2f6eb7 \ - --hash=sha256:edae79245293e15384b51f88b00613ba9f7198016a5948b5dddf4917d4d26382 \ - --hash=sha256:f1e22e8c4419538cb197e4dd60acc919d7696e5ef98ee4da4e01d3f8cfa4cc5a \ - --hash=sha256:f3a2b4222ce6b60e2e8b337bb9596923045681d71e5a082783484d845390938e \ - --hash=sha256:f6a16c31041f09ead72d69f583767292f750d24913dadacf5756b966aacb3f1a \ - --hash=sha256:f75c7ab1f9e4aca5414ed4d8e5c0e303a34f4421f8a0d47a4d019ceff0ab6af4 \ - --hash=sha256:f79fc4fc25f1c8698ff97788206bb3c2598949bfe0fef03d299eb1b5356ada99 \ - --hash=sha256:f7f5baafcc48261359e14bcd6d9bff6d4b28d9103847c9e136694cb0501aef87 \ - --hash=sha256:fc48c783f9c87e60831201f2cce7f3b2e4846bf4d8728eabe54d60700b318a0b +cffi==1.17.1 # via # cryptography # weasyprint -charset-normalizer==3.4.1 \ - --hash=sha256:0167ddc8ab6508fe81860a57dd472b2ef4060e8d378f0cc555707126830f2537 \ - --hash=sha256:01732659ba9b5b873fc117534143e4feefecf3b2078b0a6a2e925271bb6f4cfa \ - --hash=sha256:01ad647cdd609225c5350561d084b42ddf732f4eeefe6e678765636791e78b9a \ - --hash=sha256:04432ad9479fa40ec0f387795ddad4437a2b50417c69fa275e212933519ff294 \ - --hash=sha256:0907f11d019260cdc3f94fbdb23ff9125f6b5d1039b76003b5b0ac9d6a6c9d5b \ - --hash=sha256:0924e81d3d5e70f8126529951dac65c1010cdf117bb75eb02dd12339b57749dd \ - --hash=sha256:09b26ae6b1abf0d27570633b2b078a2a20419c99d66fb2823173d73f188ce601 \ - --hash=sha256:09b5e6733cbd160dcc09589227187e242a30a49ca5cefa5a7edd3f9d19ed53fd \ - --hash=sha256:0af291f4fe114be0280cdd29d533696a77b5b49cfde5467176ecab32353395c4 \ - --hash=sha256:0f55e69f030f7163dffe9fd0752b32f070566451afe180f99dbeeb81f511ad8d \ - --hash=sha256:1a2bc9f351a75ef49d664206d51f8e5ede9da246602dc2d2726837620ea034b2 \ - --hash=sha256:22e14b5d70560b8dd51ec22863f370d1e595ac3d024cb8ad7d308b4cd95f8313 \ - --hash=sha256:234ac59ea147c59ee4da87a0c0f098e9c8d169f4dc2a159ef720f1a61bbe27cd \ - --hash=sha256:2369eea1ee4a7610a860d88f268eb39b95cb588acd7235e02fd5a5601773d4fa \ - --hash=sha256:237bdbe6159cff53b4f24f397d43c6336c6b0b42affbe857970cefbb620911c8 \ - --hash=sha256:28bf57629c75e810b6ae989f03c0828d64d6b26a5e205535585f96093e405ed1 \ - --hash=sha256:2967f74ad52c3b98de4c3b32e1a44e32975e008a9cd2a8cc8966d6a5218c5cb2 \ - --hash=sha256:2a75d49014d118e4198bcee5ee0a6f25856b29b12dbf7cd012791f8a6cc5c496 \ - --hash=sha256:2bdfe3ac2e1bbe5b59a1a63721eb3b95fc9b6817ae4a46debbb4e11f6232428d \ - --hash=sha256:2d074908e1aecee37a7635990b2c6d504cd4766c7bc9fc86d63f9c09af3fa11b \ - --hash=sha256:2fb9bd477fdea8684f78791a6de97a953c51831ee2981f8e4f583ff3b9d9687e \ - --hash=sha256:311f30128d7d333eebd7896965bfcfbd0065f1716ec92bd5638d7748eb6f936a \ - --hash=sha256:329ce159e82018d646c7ac45b01a430369d526569ec08516081727a20e9e4af4 \ - --hash=sha256:345b0426edd4e18138d6528aed636de7a9ed169b4aaf9d61a8c19e39d26838ca \ - --hash=sha256:363e2f92b0f0174b2f8238240a1a30142e3db7b957a5dd5689b0e75fb717cc78 \ - --hash=sha256:3a3bd0dcd373514dcec91c411ddb9632c0d7d92aed7093b8c3bbb6d69ca74408 \ - --hash=sha256:3bed14e9c89dcb10e8f3a29f9ccac4955aebe93c71ae803af79265c9ca5644c5 \ - --hash=sha256:44251f18cd68a75b56585dd00dae26183e102cd5e0f9f1466e6df5da2ed64ea3 \ - --hash=sha256:44ecbf16649486d4aebafeaa7ec4c9fed8b88101f4dd612dcaf65d5e815f837f \ - --hash=sha256:4532bff1b8421fd0a320463030c7520f56a79c9024a4e88f01c537316019005a \ - --hash=sha256:49402233c892a461407c512a19435d1ce275543138294f7ef013f0b63d5d3765 \ - --hash=sha256:4c0907b1928a36d5a998d72d64d8eaa7244989f7aaaf947500d3a800c83a3fd6 \ - --hash=sha256:4d86f7aff21ee58f26dcf5ae81a9addbd914115cdebcbb2217e4f0ed8982e146 \ - --hash=sha256:5777ee0881f9499ed0f71cc82cf873d9a0ca8af166dfa0af8ec4e675b7df48e6 \ - --hash=sha256:5df196eb874dae23dcfb968c83d4f8fdccb333330fe1fc278ac5ceeb101003a9 \ - --hash=sha256:619a609aa74ae43d90ed2e89bdd784765de0a25ca761b93e196d938b8fd1dbbd \ - --hash=sha256:6e27f48bcd0957c6d4cb9d6fa6b61d192d0b13d5ef563e5f2ae35feafc0d179c \ - --hash=sha256:6ff8a4a60c227ad87030d76e99cd1698345d4491638dfa6673027c48b3cd395f \ - --hash=sha256:73d94b58ec7fecbc7366247d3b0b10a21681004153238750bb67bd9012414545 \ - --hash=sha256:7461baadb4dc00fd9e0acbe254e3d7d2112e7f92ced2adc96e54ef6501c5f176 \ - --hash=sha256:75832c08354f595c760a804588b9357d34ec00ba1c940c15e31e96d902093770 \ - --hash=sha256:7709f51f5f7c853f0fb938bcd3bc59cdfdc5203635ffd18bf354f6967ea0f824 \ - --hash=sha256:78baa6d91634dfb69ec52a463534bc0df05dbd546209b79a3880a34487f4b84f \ - --hash=sha256:7974a0b5ecd505609e3b19742b60cee7aa2aa2fb3151bc917e6e2646d7667dcf \ - --hash=sha256:7a4f97a081603d2050bfaffdefa5b02a9ec823f8348a572e39032caa8404a487 \ - --hash=sha256:7b1bef6280950ee6c177b326508f86cad7ad4dff12454483b51d8b7d673a2c5d \ - --hash=sha256:7d053096f67cd1241601111b698f5cad775f97ab25d81567d3f59219b5f1adbd \ - --hash=sha256:804a4d582ba6e5b747c625bf1255e6b1507465494a40a2130978bda7b932c90b \ - --hash=sha256:807f52c1f798eef6cf26beb819eeb8819b1622ddfeef9d0977a8502d4db6d534 \ - --hash=sha256:80ed5e856eb7f30115aaf94e4a08114ccc8813e6ed1b5efa74f9f82e8509858f \ - --hash=sha256:8417cb1f36cc0bc7eaba8ccb0e04d55f0ee52df06df3ad55259b9a323555fc8b \ - --hash=sha256:8436c508b408b82d87dc5f62496973a1805cd46727c34440b0d29d8a2f50a6c9 \ - --hash=sha256:89149166622f4db9b4b6a449256291dc87a99ee53151c74cbd82a53c8c2f6ccd \ - --hash=sha256:8bfa33f4f2672964266e940dd22a195989ba31669bd84629f05fab3ef4e2d125 \ - --hash=sha256:8c60ca7339acd497a55b0ea5d506b2a2612afb2826560416f6894e8b5770d4a9 \ - --hash=sha256:91b36a978b5ae0ee86c394f5a54d6ef44db1de0815eb43de826d41d21e4af3de \ - --hash=sha256:955f8851919303c92343d2f66165294848d57e9bba6cf6e3625485a70a038d11 \ - --hash=sha256:97f68b8d6831127e4787ad15e6757232e14e12060bec17091b85eb1486b91d8d \ - --hash=sha256:9b23ca7ef998bc739bf6ffc077c2116917eabcc901f88da1b9856b210ef63f35 \ - --hash=sha256:9f0b8b1c6d84c8034a44893aba5e767bf9c7a211e313a9605d9c617d7083829f \ - --hash=sha256:aabfa34badd18f1da5ec1bc2715cadc8dca465868a4e73a0173466b688f29dda \ - --hash=sha256:ab36c8eb7e454e34e60eb55ca5d241a5d18b2c6244f6827a30e451c42410b5f7 \ - --hash=sha256:b010a7a4fd316c3c484d482922d13044979e78d1861f0e0650423144c616a46a \ - --hash=sha256:b1ac5992a838106edb89654e0aebfc24f5848ae2547d22c2c3f66454daa11971 \ - --hash=sha256:b7b2d86dd06bfc2ade3312a83a5c364c7ec2e3498f8734282c6c3d4b07b346b8 \ - --hash=sha256:b97e690a2118911e39b4042088092771b4ae3fc3aa86518f84b8cf6888dbdb41 \ - --hash=sha256:bc2722592d8998c870fa4e290c2eec2c1569b87fe58618e67d38b4665dfa680d \ - --hash=sha256:c0429126cf75e16c4f0ad00ee0eae4242dc652290f940152ca8c75c3a4b6ee8f \ - --hash=sha256:c30197aa96e8eed02200a83fba2657b4c3acd0f0aa4bdc9f6c1af8e8962e0757 \ - --hash=sha256:c4c3e6da02df6fa1410a7680bd3f63d4f710232d3139089536310d027950696a \ - --hash=sha256:c75cb2a3e389853835e84a2d8fb2b81a10645b503eca9bcb98df6b5a43eb8886 \ - --hash=sha256:c96836c97b1238e9c9e3fe90844c947d5afbf4f4c92762679acfe19927d81d77 \ - --hash=sha256:d7f50a1f8c450f3925cb367d011448c39239bb3eb4117c36a6d354794de4ce76 \ - --hash=sha256:d973f03c0cb71c5ed99037b870f2be986c3c05e63622c017ea9816881d2dd247 \ - --hash=sha256:d98b1668f06378c6dbefec3b92299716b931cd4e6061f3c875a71ced1780ab85 \ - --hash=sha256:d9c3cdf5390dcd29aa8056d13e8e99526cda0305acc038b96b30352aff5ff2bb \ - --hash=sha256:dad3e487649f498dd991eeb901125411559b22e8d7ab25d3aeb1af367df5efd7 \ - --hash=sha256:dccbe65bd2f7f7ec22c4ff99ed56faa1e9f785482b9bbd7c717e26fd723a1d1e \ - --hash=sha256:dd78cfcda14a1ef52584dbb008f7ac81c1328c0f58184bf9a84c49c605002da6 \ - --hash=sha256:e218488cd232553829be0664c2292d3af2eeeb94b32bea483cf79ac6a694e037 \ - --hash=sha256:e358e64305fe12299a08e08978f51fc21fac060dcfcddd95453eabe5b93ed0e1 \ - --hash=sha256:ea0d8d539afa5eb2728aa1932a988a9a7af94f18582ffae4bc10b3fbdad0626e \ - --hash=sha256:eab677309cdb30d047996b36d34caeda1dc91149e4fdca0b1a039b3f79d9a807 \ - --hash=sha256:eb8178fe3dba6450a3e024e95ac49ed3400e506fd4e9e5c32d30adda88cbd407 \ - --hash=sha256:ecddf25bee22fe4fe3737a399d0d177d72bc22be6913acfab364b40bce1ba83c \ - --hash=sha256:eea6ee1db730b3483adf394ea72f808b6e18cf3cb6454b4d86e04fa8c4327a12 \ - --hash=sha256:f08ff5e948271dc7e18a35641d2f11a4cd8dfd5634f55228b691e62b37125eb3 \ - --hash=sha256:f30bf9fd9be89ecb2360c7d94a711f00c09b976258846efe40db3d05828e8089 \ - --hash=sha256:fa88b843d6e211393a37219e6a1c1df99d35e8fd90446f1118f4216e307e48cd \ - --hash=sha256:fc54db6c8593ef7d4b2a331b58653356cf04f67c960f584edb7c3d8c97e8f39e \ - --hash=sha256:fd4ec41f914fa74ad1b8304bbc634b3de73d2a0889bd32076342a573e0779e00 \ - --hash=sha256:ffc9202a29ab3920fa812879e95a9e78b2465fd10be7fcbd042899695d75e616 +charset-normalizer==3.4.1 # via requests -coreapi==2.3.3 \ - --hash=sha256:46145fcc1f7017c076a2ef684969b641d18a2991051fddec9458ad3f78ffc1cb \ - --hash=sha256:bf39d118d6d3e171f10df9ede5666f63ad80bba9a29a8ec17726a66cf52ee6f3 +coreapi==2.3.3 # via -r src/backend/requirements.in -coreschema==0.0.4 \ - --hash=sha256:5e6ef7bf38c1525d5e55a895934ab4273548629f16aed5c0a6caa74ebf45551f \ - --hash=sha256:9503506007d482ab0867ba14724b93c18a33b22b6d19fb419ef2d239dd4a1607 +coreschema==0.0.4 # via coreapi -cryptography==43.0.3 \ - --hash=sha256:0c580952eef9bf68c4747774cde7ec1d85a6e61de97281f2dba83c7d2c806362 \ - --hash=sha256:0f996e7268af62598f2fc1204afa98a3b5712313a55c4c9d434aef49cadc91d4 \ - --hash=sha256:1ec0bcf7e17c0c5669d881b1cd38c4972fade441b27bda1051665faaa89bdcaa \ - --hash=sha256:281c945d0e28c92ca5e5930664c1cefd85efe80e5c0d2bc58dd63383fda29f83 \ - --hash=sha256:2ce6fae5bdad59577b44e4dfed356944fbf1d925269114c28be377692643b4ff \ - --hash=sha256:315b9001266a492a6ff443b61238f956b214dbec9910a081ba5b6646a055a805 \ - --hash=sha256:443c4a81bb10daed9a8f334365fe52542771f25aedaf889fd323a853ce7377d6 \ - --hash=sha256:4a02ded6cd4f0a5562a8887df8b3bd14e822a90f97ac5e544c162899bc467664 \ - --hash=sha256:53a583b6637ab4c4e3591a15bc9db855b8d9dee9a669b550f311480acab6eb08 \ - --hash=sha256:63efa177ff54aec6e1c0aefaa1a241232dcd37413835a9b674b6e3f0ae2bfd3e \ - --hash=sha256:74f57f24754fe349223792466a709f8e0c093205ff0dca557af51072ff47ab18 \ - --hash=sha256:7e1ce50266f4f70bf41a2c6dc4358afadae90e2a1e5342d3c08883df1675374f \ - --hash=sha256:81ef806b1fef6b06dcebad789f988d3b37ccaee225695cf3e07648eee0fc6b73 \ - --hash=sha256:846da004a5804145a5f441b8530b4bf35afbf7da70f82409f151695b127213d5 \ - --hash=sha256:8ac43ae87929a5982f5948ceda07001ee5e83227fd69cf55b109144938d96984 \ - --hash=sha256:9762ea51a8fc2a88b70cf2995e5675b38d93bf36bd67d91721c309df184f49bd \ - --hash=sha256:a2a431ee15799d6db9fe80c82b055bae5a752bef645bba795e8e52687c69efe3 \ - --hash=sha256:bf7a1932ac4176486eab36a19ed4c0492da5d97123f1406cf15e41b05e787d2e \ - --hash=sha256:c2e6fc39c4ab499049df3bdf567f768a723a5e8464816e8f009f121a5a9f4405 \ - --hash=sha256:cbeb489927bd7af4aa98d4b261af9a5bc025bd87f0e3547e11584be9e9427be2 \ - --hash=sha256:d03b5621a135bffecad2c73e9f4deb1a0f977b9a8ffe6f8e002bf6c9d07b918c \ - --hash=sha256:d56e96520b1020449bbace2b78b603442e7e378a9b3bd68de65c782db1507995 \ - --hash=sha256:df6b6c6d742395dd77a23ea3728ab62f98379eff8fb61be2744d4679ab678f73 \ - --hash=sha256:e1be4655c7ef6e1bbe6b5d0403526601323420bcf414598955968c9ef3eb7d16 \ - --hash=sha256:f18c716be16bc1fea8e95def49edf46b82fccaa88587a45f8dc0ff6ab5d8e0a7 \ - --hash=sha256:f46304d6f0c6ab8e52770addfa2fc41e6629495548862279641972b6215451cd \ - --hash=sha256:f7b178f11ed3664fd0e995a47ed2b5ff0a12d893e41dd0494f406d1cf555cab7 +cryptography==43.0.3 # via # -r src/backend/requirements.in # djangorestframework-simplejwt # fido2 # pyjwt -cssselect2==0.7.0 \ - --hash=sha256:1ccd984dab89fc68955043aca4e1b03e0cf29cad9880f6e28e3ba7a74b14aa5a \ - --hash=sha256:fd23a65bfd444595913f02fc71f6b286c29261e354c41d722ca7a261a49b5969 +cssselect2==0.7.0 # via weasyprint -defusedxml==0.7.1 \ - --hash=sha256:1bb3032db185915b62d7c6209c5a8792be6a32ab2fedacc84e01b52c51aa3e69 \ - --hash=sha256:a352e7e428770286cc899e2542b6cdaedb2b4953ff269a210103ec58f6198a61 +defusedxml==0.7.1 # via python3-openid -deprecated==1.2.15 \ - --hash=sha256:353bc4a8ac4bfc96800ddab349d89c25dec1079f65fd53acdcc1e0b975b21320 \ - --hash=sha256:683e561a90de76239796e6b6feac66b99030d2dd3fcf61ef996330f14bbb9b0d +deprecated==1.2.15 # via # opentelemetry-api # opentelemetry-exporter-otlp-proto-grpc # opentelemetry-exporter-otlp-proto-http # opentelemetry-semantic-conventions -django==4.2.18 \ - --hash=sha256:52ae8eacf635617c0f13b44f749e5ea13dc34262819b2cc8c8636abb08d82c4b \ - --hash=sha256:ba52eff7e228f1c775d5b0db2ba53d8c49d2f8bfe6ca0234df6b7dd12fb25b19 +django==4.2.18 # via # -r src/backend/requirements.in # django-allauth @@ -414,574 +79,129 @@ django==4.2.18 \ # drf-spectacular django-allauth[mfa, openid, saml, socialaccount] @ git+https://codeberg.org/allauth/django-allauth@b802a0ff6206a8b7b6c9ecf7148d414f6868d47a # via -r src/backend/requirements.in -django-cleanup==9.0.0 \ - --hash=sha256:19f8b0e830233f9f0f683b17181f414672a0f48afe3ea3cc80ba47ae40ad880c \ - --hash=sha256:bb9fb560aaf62959c81e31fa40885c36bbd5854d5aa21b90df2c7e4ba633531e +django-cleanup==9.0.0 # via -r src/backend/requirements.in -django-cors-headers==4.6.0 \ - --hash=sha256:14d76b4b4c8d39375baeddd89e4f08899051eeaf177cb02a29bd6eae8cf63aa8 \ - --hash=sha256:8edbc0497e611c24d5150e0055d3b178c6534b8ed826fb6f53b21c63f5d48ba3 +django-cors-headers==4.6.0 # via -r src/backend/requirements.in -django-dbbackup==4.2.1 \ - --hash=sha256:157a2ec10d482345cd75092e510ac40d6e2ee6084604a1d17abe178c2f06bc69 \ - --hash=sha256:b23265600ead0780ca781b1b4b594949aaa8a20d74f08701f91ee9d7eb1f08cd +django-dbbackup==4.2.1 # via -r src/backend/requirements.in -django-error-report-2==0.4.2 \ - --hash=sha256:1dd99c497af09b7ea99f5fbaf910501838150a9d5390796ea00e187bc62f6c1b \ - --hash=sha256:603e1e3b24d01bbfeab6379af948893b2b034031c80fa8b45cf1c4735341c04b +django-error-report-2==0.4.2 # via -r src/backend/requirements.in -django-filter==24.3 \ - --hash=sha256:c4852822928ce17fb699bcfccd644b3574f1a2d80aeb2b4ff4f16b02dd49dc64 \ - --hash=sha256:d8ccaf6732afd21ca0542f6733b11591030fa98669f8d15599b358e24a2cd9c3 +django-filter==24.3 # via -r src/backend/requirements.in -django-flags==5.0.13 \ - --hash=sha256:52df74b86d93f5cb402190ad26b68a5ba0f127e9e016189f1a6f2e8ba3c06a42 \ - --hash=sha256:ff6940cf37e07d6d0c4ac28c5420c8cfc478b62541473dba4aa02d600f7db9fc +django-flags==5.0.13 # via -r src/backend/requirements.in -django-ical==1.9.2 \ - --hash=sha256:44c9b6fa90d09f25e9ebaa91ed9eb007f079afbc23d6aac909cfc18188a8e90c \ - --hash=sha256:74a16bca05735f91a00120cad7250f3c3aa292a9f698a6cfdc544a922c11de70 +django-ical==1.9.2 # via -r src/backend/requirements.in -django-ipware==7.0.1 \ - --hash=sha256:d9ec43d2bf7cdf216fed8d494a084deb5761a54860a53b2e74346a4f384cff47 \ - --hash=sha256:db16bbee920f661ae7f678e4270460c85850f03c6761a4eaeb489bdc91f64709 +django-ipware==7.0.1 # via django-structlog -django-js-asset==2.2.0 \ - --hash=sha256:0c57a82cae2317e83951d956110ce847f58ff0cdc24e314dbc18b35033917e94 \ - --hash=sha256:7ef3e858e13d06f10799b56eea62b1e76706f42cf4e709be4e13356bc0ae30d8 +django-js-asset==2.2.0 # via django-mptt -django-maintenance-mode==0.21.1 \ - --hash=sha256:b79afddb671c59972ae542e4fafbc99117d2d37991843eaaa837e328eed12b1b \ - --hash=sha256:c02fff0e386b7f8b2ab54479d3a0d336ae34014da22a7a2365ca96d5a2c1db94 +django-maintenance-mode==0.21.1 # via -r src/backend/requirements.in -django-markdownify==0.9.5 \ - --hash=sha256:2c4ae44e386c209453caf5e9ea1b74f64535985d338ad2d5ad5e7089cc94be86 \ - --hash=sha256:34c34eba4a797282a5c5bd97b13cec84d6a4c0673ad47ce1c1d000d74dd8d4ab +django-markdownify==0.9.5 # via -r src/backend/requirements.in -django-money==3.2.0 \ - --hash=sha256:2e4174b47993780bf4b61ad3fa0a66ebe140da42fdbe68b628c7ba9788287214 \ - --hash=sha256:3099f906407175af06b56ef3ff5c250e2fc525ff00f50d42f77b98597e625459 +django-money==3.2.0 # via -r src/backend/requirements.in -django-mptt==0.16.0 \ - --hash=sha256:56c9606bf0b329b5f5afd55dd8bfd073612ea1d5999b10903b09de62bee84c8e \ - --hash=sha256:8716849ba3318d94e2e100ed0923a05c1ffdf8195f8472b690dbaf737d2af3b5 +django-mptt==0.16.0 # via -r src/backend/requirements.in -django-otp==1.3.0 \ - --hash=sha256:5277731bc05b6cdbf96aa84ac46018e30ed5fb248086053b0146f925de059060 \ - --hash=sha256:8f4156a3c14ce2aaa31379385eadf388925cd50fc4b5d20a3b944f454c98ff7c +django-otp==1.3.0 # via -r src/backend/requirements.in -django-picklefield==3.2 \ - --hash=sha256:aa463f5d79d497dbe789f14b45180f00a51d0d670067d0729f352a3941cdfa4d \ - --hash=sha256:e9a73539d110f69825d9320db18bcb82e5189ff48dbed41821c026a20497764c +django-picklefield==3.2 # via django-q2 -django-q-sentry==0.1.6 \ - --hash=sha256:9b8b4d7fad253a7d9a47f2c2ab0d9dea83078b7ef45c8849dbb1e4176ef8d050 +django-q-sentry==0.1.6 # via -r src/backend/requirements.in -django-q2==1.7.6 \ - --hash=sha256:5210b121573cf65b97d495dbebefe6cfac394d8c0aec9ca2117e8e56e2fda17d \ - --hash=sha256:9060f4d68e1f3a8a748e0ebd0bd83c8c24bc13036105035873faab9d85b0e8f6 +django-q2==1.7.6 # via -r src/backend/requirements.in -django-recurrence==1.11.1 \ - --hash=sha256:0c65f30872599b5813a9bab6952dada23c55894f28674490a753ada559f14bc5 \ - --hash=sha256:9c89444e651a78c587f352c5f63eda48ab2f53996347b9fcdff2d248f4fcff70 +django-recurrence==1.11.1 # via django-ical -django-redis==5.4.0 \ - --hash=sha256:6a02abaa34b0fea8bf9b707d2c363ab6adc7409950b2db93602e6cb292818c42 \ - --hash=sha256:ebc88df7da810732e2af9987f7f426c96204bf89319df4c6da6ca9a2942edd5b +django-redis==5.4.0 # via -r src/backend/requirements.in -django-sesame==3.2.2 \ - --hash=sha256:523ebd4d04e28c897c262f25b78b6fd8f37e11cdca6e277fdc8bf496bd686cf5 \ - --hash=sha256:5d753a309166356b6a0d7fc047690943b9e80b4aa7952f1a6400fe6ce60d573c +django-sesame==3.2.2 # via -r src/backend/requirements.in -django-sql-utils==0.7.0 \ - --hash=sha256:9371ff28eaf326836a7c52887259123cdd3fbffb7b738e42ae1a21258be0feb6 \ - --hash=sha256:fefc40c826896b60fcf33e35b6e30b523fc958955a16006438cd3ba6d795a532 +django-sql-utils==0.7.0 # via -r src/backend/requirements.in -django-sslserver==0.22 \ - --hash=sha256:c598a363d2ccdc2421c08ddb3d8b0973f80e8e47a3a5b74e4a2896f21c2947c5 +django-sslserver==0.22 # via -r src/backend/requirements.in -django-stdimage==6.0.2 \ - --hash=sha256:880ab14828be56b53f711c3afae83c219ddd5d9af00850626736feb48382bf7f \ - --hash=sha256:9a73f7da48c48074580e2b032d5bdb7164935dbe4b9dc4fb88a7e112f3d521c8 +django-stdimage==6.0.2 # via -r src/backend/requirements.in -django-structlog==9.0.0 \ - --hash=sha256:0ada1ac0fa4e90a499fc9ce22fc164830304e7219cab12d3b5ef9c2f2967ff5b \ - --hash=sha256:8c32097b3f09b2727b746d6cdf4f50565c62fd040b3a0c98cb731de0b380b854 +django-structlog==9.0.0 # via -r src/backend/requirements.in -django-taggit==6.1.0 \ - --hash=sha256:ab776264bbc76cb3d7e49e1bf9054962457831bd21c3a42db9138b41956e4cf0 \ - --hash=sha256:c4d1199e6df34125dd36db5eb0efe545b254dec3980ce5dd80e6bab3e78757c3 +django-taggit==6.1.0 # via -r src/backend/requirements.in -django-weasyprint==2.3.1 \ - --hash=sha256:09cc1c40c92db34bed80154be7c959fea03d6001dc46fd599f3fd464d6a6dc72 \ - --hash=sha256:cd35b8bd24b28128a17a2416d0e6f3e64cb727f25c53467150b4be16ccd01c19 +django-weasyprint==2.3.1 # via -r src/backend/requirements.in -django-xforwardedfor-middleware==2.0 \ - --hash=sha256:16fd1cb27f33a5541b6f3e0b43afb1b7334a76f27a1255b69e14ec5c440f0b24 +django-xforwardedfor-middleware==2.0 # via -r src/backend/requirements.in -djangorestframework==3.14.0 \ - --hash=sha256:579a333e6256b09489cbe0a067e66abe55c6595d8926be6b99423786334350c8 \ - --hash=sha256:eb63f58c9f218e1a7d064d17a70751f528ed4e1d35547fdade9aaf4cd103fd08 +djangorestframework==3.14.0 # via # -r src/backend/requirements.in # djangorestframework-simplejwt # drf-spectacular -djangorestframework-simplejwt[crypto]==5.4.0 \ - --hash=sha256:7aec953db9ed4163430c16d086eecb0f028f814ce6bba62b06c25919261e9077 \ - --hash=sha256:cccecce1a0e1a4a240fae80da73e5fc23055bababb8b67de88fa47cd36822320 +djangorestframework-simplejwt[crypto]==5.4.0 # via -r src/backend/requirements.in -drf-spectacular==0.28.0 \ - --hash=sha256:2c778a47a40ab2f5078a7c42e82baba07397bb35b074ae4680721b2805943061 \ - --hash=sha256:856e7edf1056e49a4245e87a61e8da4baff46c83dbc25be1da2df77f354c7cb4 +drf-spectacular==0.28.0 # via -r src/backend/requirements.in -dulwich==0.22.7 \ - --hash=sha256:007d8160b511bb149d31c08548307982f6ce752a46e7088b020517de00c3bd46 \ - --hash=sha256:01544915c4056d0820de8cf126b971f7c180743ff64c4435c89168e44b30df4b \ - --hash=sha256:01e484d44014fef78cdef3b3adc34564808b4677497a57a0950c90a1d6349be3 \ - --hash=sha256:052715452b729544c611a107b2eef6111e527f041c1b666f8ed36c04e39c36b5 \ - --hash=sha256:10c5ee20430714ea6a79dde22c1f77078848930d27021aa810204738bc175e95 \ - --hash=sha256:1782854c10878b5cb8423e74b0ef4256c3667f7b0266513af028ac28dbab1f2d \ - --hash=sha256:1cbd5ecbc95e18c745965fc7b2b71209443987a99e499c7bb074234d7c6142e2 \ - --hash=sha256:2220c8b7cac5794e2260a924e81b05baa7836c18ba805d5a6731071a5ff6b860 \ - --hash=sha256:257abd49a768a52cf7f508daf2d30fe73f54fd32b7a674abd43817f66b0ca17b \ - --hash=sha256:2b7a3ac4baa49bd988cc0d0891a93aa26307c01f35caeed8729b7928a1f483af \ - --hash=sha256:40260034a6ecc3141a0d42360e888a73e58b9c0c9363c454cae182957fe602ac \ - --hash=sha256:5ada6a2fd400a4f51adfedd0267bfb08c61e2d9846c18ea653b0eb88a7b851d0 \ - --hash=sha256:5b9806a75f4b74fa891926b1d830e21f9cead80ed6dd803ed668369b26fb8b5f \ - --hash=sha256:62027dfccee97268eadf0c54df3d72ce30e4402cf5cf06c021e474b9a9eb3536 \ - --hash=sha256:637a9ac27512b8c04e6a29bf92e3f73386cd85dfe8609f523ffbc96e659bde4b \ - --hash=sha256:6bda2eca0847c30a9312a72f219af9e63feb7d2ca89f47fdaa240b0d0cdd6b84 \ - --hash=sha256:6bea11b98e854ff2abec390eeac752586b83921a22091dae65470ccbb003fc1b \ - --hash=sha256:6c830d63c691b5f979964a2d6b325930b7a53f14836598352690601cd205f04b \ - --hash=sha256:71b20bd6a25658e968e813eb69164332d3a2ab6029b51d3c6af8b64f2471847a \ - --hash=sha256:74b7cf6f0d46ac777be617dad7c1b992380004de74c0e0652bed174686249f34 \ - --hash=sha256:753eec461434f0ccbe0956ec825250e12230e8f1b365c8be1604386d94c2d8d0 \ - --hash=sha256:7649f0c9b4760d72768805155e66579761f282fdca123e351019c85efce811eb \ - --hash=sha256:7d72ce1377eac23bd77aa3541ceb91f2d8bd68687659f8625af8301f0b6b0a63 \ - --hash=sha256:8dd5df3919c648887e550e836f87b4b83f1429876adce5ead5b5977e333c874d \ - --hash=sha256:925cec97aeefda3f950e45e8d4c247e4ce6f83b6ee96e383c82f9bced626151f \ - --hash=sha256:986943e27a5c94c0be42fdcc688be1ae1a1349a3dbaa773fa7f9bdada1232b68 \ - --hash=sha256:9c01db2ef6d5f5b9192c0011624701b0de328868fe0c32601368cd337e77cd1a \ - --hash=sha256:9f418779837a3249b7dfc4b3dc7266fa40687e5f0249eedfa7185560ba1ee148 \ - --hash=sha256:9f5954cd491313743d7bd3623d323b72afceb83d2c2a47921f621bdd9d4c615b \ - --hash=sha256:a64e61fa6ab60db0f897f1c30f32b26b330d3a9dc264f089ee9c44f5900fb657 \ - --hash=sha256:a8886b2c9750ba15193356d9e8608e031cd89a780d0afc53b3101391605b3793 \ - --hash=sha256:aa0bb9afa799c0301b2760e9af99083a2b08f655c55037945b6a5e227566adc1 \ - --hash=sha256:b25848041c51d09affafd2708236205cc4483bed8f7f43ecbe63b6a66b447604 \ - --hash=sha256:bb258c62d7fb4cfe03b3fba09f702ebb84a924f2f004833435e32c93fe8a7f13 \ - --hash=sha256:c68ab3540809bedcdd9b99e51c12adf11c2ab26554f74d899d8cf55bfa2639a6 \ - --hash=sha256:ca7ed207956001e6a8a2e3f319cdc37591e53f7eb04aedafa78f96768048c53e \ - --hash=sha256:cdbcf206d4b1e5ba2affc6189948cb292cc647593876b96a0b71db44e79a05a1 \ - --hash=sha256:d53935832dd182d4c1415042187093efcee988af5cd397fb1f394f5bb27f0707 \ - --hash=sha256:df5a179e5d95ac0263b5e0ccd53311eac486091979dcac106c5cc9e0ee4f2aa2 \ - --hash=sha256:f73668ecc29e0a20d20970489fffe2ba466e5486eae2f20104bc38bcbe611f64 \ - --hash=sha256:fdbd087e9e99bc809b15864ebc79dbefe869e3038b64c953d7736f6e6b382dc7 \ - --hash=sha256:fe324dc40b93e8be996c9fa9291a439bef835a92a2e4cb5c8cbdb1171c168fd6 +dulwich==0.22.7 # via -r src/backend/requirements.in -et-xmlfile==2.0.0 \ - --hash=sha256:7a91720bc756843502c3b7504c77b8fe44217c85c537d85037f0f536151b2caa \ - --hash=sha256:dab3f4764309081ce75662649be815c4c9081e88f0837825f90fd28317d4da54 +et-xmlfile==2.0.0 # via openpyxl -feedparser==6.0.11 \ - --hash=sha256:0be7ee7b395572b19ebeb1d6aafb0028dee11169f1c934e0ed67d54992f4ad45 \ - --hash=sha256:c9d0407b64c6f2a065d0ebb292c2b35c01050cc0dc33757461aaabdc4c4184d5 +feedparser==6.0.11 # via -r src/backend/requirements.in -fido2==1.2.0 \ - --hash=sha256:e39f95920122d64283fda5e5581d95a206e704fa42846bfa4662f86aa0d3333b \ - --hash=sha256:f7c8ee62e359aa980a45773f9493965bb29ede1b237a9218169dbfe60c80e130 +fido2==1.2.0 # via django-allauth -flexcache==0.3 \ - --hash=sha256:18743bd5a0621bfe2cf8d519e4c3bfdf57a269c15d1ced3fb4b64e0ff4600656 \ - --hash=sha256:d43c9fea82336af6e0115e308d9d33a185390b8346a017564611f1466dcd2e32 +flexcache==0.3 # via pint -flexparser==0.4 \ - --hash=sha256:266d98905595be2ccc5da964fe0a2c3526fbbffdc45b65b3146d75db992ef6b2 \ - --hash=sha256:3738b456192dcb3e15620f324c447721023c0293f6af9955b481e91d00179846 +flexparser==0.4 # via pint -fonttools[woff]==4.55.3 \ - --hash=sha256:07f8288aacf0a38d174445fc78377a97fb0b83cfe352a90c9d9c1400571963c7 \ - --hash=sha256:11e5de1ee0d95af4ae23c1a138b184b7f06e0b6abacabf1d0db41c90b03d834b \ - --hash=sha256:1bc7ad24ff98846282eef1cbeac05d013c2154f977a79886bb943015d2b1b261 \ - --hash=sha256:1dcc07934a2165ccdc3a5a608db56fb3c24b609658a5b340aee4ecf3ba679dc0 \ - --hash=sha256:22f38464daa6cdb7b6aebd14ab06609328fe1e9705bb0fcc7d1e69de7109ee02 \ - --hash=sha256:27e4ae3592e62eba83cd2c4ccd9462dcfa603ff78e09110680a5444c6925d841 \ - --hash=sha256:3983313c2a04d6cc1fe9251f8fc647754cf49a61dac6cb1e7249ae67afaafc45 \ - --hash=sha256:529cef2ce91dc44f8e407cc567fae6e49a1786f2fefefa73a294704c415322a4 \ - --hash=sha256:5323a22eabddf4b24f66d26894f1229261021dacd9d29e89f7872dd8c63f0b8b \ - --hash=sha256:54153c49913f45065c8d9e6d0c101396725c5621c8aee744719300f79771d75a \ - --hash=sha256:546565028e244a701f73df6d8dd6be489d01617863ec0c6a42fa25bf45d43048 \ - --hash=sha256:5480673f599ad410695ca2ddef2dfefe9df779a9a5cda89503881e503c9c7d90 \ - --hash=sha256:5e8d657cd7326eeaba27de2740e847c6b39dde2f8d7cd7cc56f6aad404ddf0bd \ - --hash=sha256:62d65a3022c35e404d19ca14f291c89cc5890032ff04f6c17af0bd1927299674 \ - --hash=sha256:6314bf82c54c53c71805318fcf6786d986461622dd926d92a465199ff54b1b72 \ - --hash=sha256:7a8aa2c5e5b8b3bcb2e4538d929f6589a5c6bdb84fd16e2ed92649fb5454f11c \ - --hash=sha256:827e95fdbbd3e51f8b459af5ea10ecb4e30af50221ca103bea68218e9615de07 \ - --hash=sha256:859c358ebf41db18fb72342d3080bce67c02b39e86b9fbcf1610cca14984841b \ - --hash=sha256:86721fbc389ef5cc1e2f477019e5069e8e4421e8d9576e9c26f840dbb04678de \ - --hash=sha256:89bdc5d88bdeec1b15af790810e267e8332d92561dce4f0748c2b95c9bdf3926 \ - --hash=sha256:8c4491699bad88efe95772543cd49870cf756b019ad56294f6498982408ab03e \ - --hash=sha256:8c5ec45428edaa7022f1c949a632a6f298edc7b481312fc7dc258921e9399628 \ - --hash=sha256:8e75f12c82127486fac2d8bfbf5bf058202f54bf4f158d367e41647b972342ca \ - --hash=sha256:a430178ad3e650e695167cb53242dae3477b35c95bef6525b074d87493c4bf29 \ - --hash=sha256:a8c2794ded89399cc2169c4d0bf7941247b8d5932b2659e09834adfbb01589aa \ - --hash=sha256:aca318b77f23523309eec4475d1fbbb00a6b133eb766a8bdc401faba91261abe \ - --hash=sha256:ae3b6600565b2d80b7c05acb8e24d2b26ac407b27a3f2e078229721ba5698427 \ - --hash=sha256:aedbeb1db64496d098e6be92b2e63b5fac4e53b1b92032dfc6988e1ea9134a4d \ - --hash=sha256:aee3b57643827e237ff6ec6d28d9ff9766bd8b21e08cd13bff479e13d4b14765 \ - --hash=sha256:b54baf65c52952db65df39fcd4820668d0ef4766c0ccdf32879b77f7c804d5c5 \ - --hash=sha256:b586ab5b15b6097f2fb71cafa3c98edfd0dba1ad8027229e7b1e204a58b0e09d \ - --hash=sha256:b8d5e8916c0970fbc0f6f1bece0063363bb5857a7f170121a4493e31c3db3314 \ - --hash=sha256:bc5dbb4685e51235ef487e4bd501ddfc49be5aede5e40f4cefcccabc6e60fb4b \ - --hash=sha256:bdcc9f04b36c6c20978d3f060e5323a43f6222accc4e7fcbef3f428e216d96af \ - --hash=sha256:c3ca99e0d460eff46e033cd3992a969658c3169ffcd533e0a39c63a38beb6831 \ - --hash=sha256:caf8230f3e10f8f5d7593eb6d252a37caf58c480b19a17e250a63dad63834cf3 \ - --hash=sha256:cd70de1a52a8ee2d1877b6293af8a2484ac82514f10b1c67c1c5762d38073e56 \ - --hash=sha256:cf4fe7c124aa3f4e4c1940880156e13f2f4d98170d35c749e6b4f119a872551e \ - --hash=sha256:d342e88764fb201286d185093781bf6628bbe380a913c24adf772d901baa8276 \ - --hash=sha256:da9da6d65cd7aa6b0f806556f4985bcbf603bf0c5c590e61b43aa3e5a0f822d0 \ - --hash=sha256:dc5294a3d5c84226e3dbba1b6f61d7ad813a8c0238fceea4e09aa04848c3d851 \ - --hash=sha256:dd68c87a2bfe37c5b33bcda0fba39b65a353876d3b9006fde3adae31f97b3ef5 \ - --hash=sha256:e6e8766eeeb2de759e862004aa11a9ea3d6f6d5ec710551a88b476192b64fd54 \ - --hash=sha256:e894b5bd60d9f473bed7a8f506515549cc194de08064d829464088d23097331b \ - --hash=sha256:eb6ca911c4c17eb51853143624d8dc87cdcdf12a711fc38bf5bd21521e79715f \ - --hash=sha256:ed63959d00b61959b035c7d47f9313c2c1ece090ff63afea702fe86de00dbed4 \ - --hash=sha256:f412604ccbeee81b091b420272841e5ec5ef68967a9790e80bffd0e30b8e2977 \ - --hash=sha256:f7d66c15ba875432a2d2fb419523f5d3d347f91f48f57b8b08a2dfc3c39b8a3f \ - --hash=sha256:f9e736f60f4911061235603a6119e72053073a12c6d7904011df2d8fad2c0e35 \ - --hash=sha256:fb594b5a99943042c702c550d5494bdd7577f6ef19b0bc73877c948a63184a32 +fonttools[woff]==4.55.3 # via weasyprint -googleapis-common-protos==1.66.0 \ - --hash=sha256:c3e7b33d15fdca5374cc0a7346dd92ffa847425cc4ea941d970f13680052ec8c \ - --hash=sha256:d7abcd75fabb2e0ec9f74466401f6c119a0b498e27370e9be4c94cb7e382b8ed +googleapis-common-protos==1.66.0 # via # opentelemetry-exporter-otlp-proto-grpc # opentelemetry-exporter-otlp-proto-http -grpcio==1.69.0 \ - --hash=sha256:01f834732c22a130bdf3dc154d1053bdbc887eb3ccb7f3e6285cfbfc33d9d5cc \ - --hash=sha256:028337786f11fecb5d7b7fa660475a06aabf7e5e52b5ac2df47414878c0ce7ea \ - --hash=sha256:0470fa911c503af59ec8bc4c82b371ee4303ececbbdc055f55ce48e38b20fd67 \ - --hash=sha256:0f0270bd9ffbff6961fe1da487bdcd594407ad390cc7960e738725d4807b18c4 \ - --hash=sha256:1227ff7836f7b3a4ab04e5754f1d001fa52a730685d3dc894ed8bc262cc96c01 \ - --hash=sha256:1514341def9c6ec4b7f0b9628be95f620f9d4b99331b7ef0a1845fd33d9b579c \ - --hash=sha256:1e925954b18d41aeb5ae250262116d0970893b38232689c4240024e4333ac084 \ - --hash=sha256:1ee76cd7e2e49cf9264f6812d8c9ac1b85dda0eaea063af07292400f9191750e \ - --hash=sha256:1f03dc9b4da4c0dc8a1db7a5420f575251d7319b7a839004d8916257ddbe4816 \ - --hash=sha256:200e48a6e7b00f804cf00a1c26292a5baa96507c7749e70a3ec10ca1a288936e \ - --hash=sha256:2060ca95a8db295ae828d0fc1c7f38fb26ccd5edf9aa51a0f44251f5da332e97 \ - --hash=sha256:26c9a9c4ac917efab4704b18eed9082ed3b6ad19595f047e8173b5182fec0d5e \ - --hash=sha256:282f47d0928e40f25d007f24eb8fa051cb22551e3c74b8248bc9f9bea9c35fe0 \ - --hash=sha256:2e52e107261fd8fa8fa457fe44bfadb904ae869d87c1280bf60f93ecd3e79278 \ - --hash=sha256:316463c0832d5fcdb5e35ff2826d9aa3f26758d29cdfb59a368c1d6c39615a11 \ - --hash=sha256:3629d8a8185f5139869a6a17865d03113a260e311e78fbe313f1a71603617589 \ - --hash=sha256:3b75aea7c6cb91b341c85e7c1d9db1e09e1dd630b0717f836be94971e015031e \ - --hash=sha256:45a4704339b6e5b24b0e136dea9ad3815a94f30eb4f1e1d44c4ac484ef11d8dd \ - --hash=sha256:4ed866f9edb574fd9be71bf64c954ce1b88fc93b2a4cbf94af221e9426eb14d6 \ - --hash=sha256:5494d0e52bf77a2f7eb17c6da662886ca0a731e56c1c85b93505bece8dc6cf4c \ - --hash=sha256:5ccbed100dc43704e94ccff9e07680b540d64e4cc89213ab2832b51b4f68a520 \ - --hash=sha256:5cfd14175f9db33d4b74d63de87c64bb0ee29ce475ce3c00c01ad2a3dc2a9e51 \ - --hash=sha256:60e5de105dc02832dc8f120056306d0ef80932bcf1c0e2b4ca3b676de6dc6505 \ - --hash=sha256:7e76accf38808f5c5c752b0ab3fd919eb14ff8fafb8db520ad1cc12afff74de6 \ - --hash=sha256:85d347cb8237751b23539981dbd2d9d8f6e9ff90082b427b13022b948eb6347a \ - --hash=sha256:87d222569273720366f68a99cb62e6194681eb763ee1d3b1005840678d4884f9 \ - --hash=sha256:8b94e83f66dbf6fd642415faca0608590bc5e8d30e2c012b31d7d1b91b1de2fd \ - --hash=sha256:8cc614e895177ab7e4b70f154d1a7c97e152577ea101d76026d132b7aaba003b \ - --hash=sha256:8de1b192c29b8ce45ee26a700044717bcbbd21c697fa1124d440548964328561 \ - --hash=sha256:9031069d36cb949205293cf0e243abd5e64d6c93e01b078c37921493a41b72dc \ - --hash=sha256:90b3646ced2eae3a0599658eeccc5ba7f303bf51b82514c50715bdd2b109e5ec \ - --hash=sha256:936fa44241b5379c5afc344e1260d467bee495747eaf478de825bab2791da6f5 \ - --hash=sha256:a78a06911d4081a24a1761d16215a08e9b6d4d29cdbb7e427e6c7e17b06bcc5d \ - --hash=sha256:a7f4ed0dcf202a70fe661329f8874bc3775c14bb3911d020d07c82c766ce0eb1 \ - --hash=sha256:b192b81076073ed46f4b4dd612b8897d9a1e39d4eabd822e5da7b38497ed77e1 \ - --hash=sha256:b62b0f41e6e01a3e5082000b612064c87c93a49b05f7602fe1b7aa9fd5171a1d \ - --hash=sha256:b634851b92c090763dde61df0868c730376cdb73a91bcc821af56ae043b09596 \ - --hash=sha256:b650f34aceac8b2d08a4c8d7dc3e8a593f4d9e26d86751ebf74ebf5107d927de \ - --hash=sha256:b7f693db593d6bf285e015d5538bf1c86cf9c60ed30b6f7da04a00ed052fe2f3 \ - --hash=sha256:bf1f8be0da3fcdb2c1e9f374f3c2d043d606d69f425cd685110dd6d0d2d61258 \ - --hash=sha256:bf5f680d3ed08c15330d7830d06bc65f58ca40c9999309517fd62880d70cb06e \ - --hash=sha256:c1fea55d26d647346acb0069b08dca70984101f2dc95066e003019207212e303 \ - --hash=sha256:c5ba38aeac7a2fe353615c6b4213d1fbb3a3c34f86b4aaa8be08baaaee8cc56d \ - --hash=sha256:c9a281878feeb9ae26db0622a19add03922a028d4db684658f16d546601a4870 \ - --hash=sha256:ca71d73a270dff052fe4edf74fef142d6ddd1f84175d9ac4a14b7280572ac519 \ - --hash=sha256:cc89b6c29f3dccbe12d7a3b3f1b3999db4882ae076c1c1f6df231d55dbd767a5 \ - --hash=sha256:cd7ea241b10bc5f0bb0f82c0d7896822b7ed122b3ab35c9851b440c1ccf81588 \ - --hash=sha256:d5658c3c2660417d82db51e168b277e0ff036d0b0f859fa7576c0ffd2aec1442 \ - --hash=sha256:db6f9fd2578dbe37db4b2994c94a1d9c93552ed77dca80e1657bb8a05b898b55 \ - --hash=sha256:dc48f99cc05e0698e689b51a05933253c69a8c8559a47f605cff83801b03af0e \ - --hash=sha256:dc5a351927d605b2721cbb46158e431dd49ce66ffbacb03e709dc07a491dde35 \ - --hash=sha256:dd034d68a2905464c49479b0c209c773737a4245d616234c79c975c7c90eca03 \ - --hash=sha256:f79e05f5bbf551c4057c227d1b041ace0e78462ac8128e2ad39ec58a382536d2 \ - --hash=sha256:fb9302afc3a0e4ba0b225cd651ef8e478bf0070cf11a529175caecd5ea2474e7 \ - --hash=sha256:fc18a4de8c33491ad6f70022af5c460b39611e39578a4d84de0fe92f12d5d47b +grpcio==1.69.0 # via # -r src/backend/requirements.in # opentelemetry-exporter-otlp-proto-grpc -gunicorn==23.0.0 \ - --hash=sha256:ec400d38950de4dfd418cff8328b2c8faed0edb0d517d3394e457c317908ca4d \ - --hash=sha256:f014447a0101dc57e294f6c18ca6b40227a4c90e9bdb586042628030cba004ec +gunicorn==23.0.0 # via -r src/backend/requirements.in -html5lib==1.1 \ - --hash=sha256:0d78f8fde1c230e99fe37986a60526d7049ed4bf8a9fadbad5f00e22e58e041d \ - --hash=sha256:b2e5b40261e20f354d198eae92afc10d750afb487ed5e50f9c4eaf07c184146f +html5lib==1.1 # via weasyprint -icalendar==6.1.0 \ - --hash=sha256:43c2db8632959d634f4e48f6e6131e706bf2cdddad488cf0b72fda079b796bad \ - --hash=sha256:46c09b774a6e6948495dafcb166dc15135c8259d0ae25491f154cbc822714b69 +icalendar==6.1.0 # via django-ical -idna==3.10 \ - --hash=sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9 \ - --hash=sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3 +idna==3.10 # via requests -importlib-metadata==8.5.0 \ - --hash=sha256:45e54197d28b7a7f1559e60b95e7c567032b602131fbd588f1497f47880aa68b \ - --hash=sha256:71522656f0abace1d072b9e5481a48f07c138e00f079c38c8f883823f9c26bd7 +importlib-metadata==8.5.0 # via # django-q2 # markdown # opentelemetry-api -inflection==0.5.1 \ - --hash=sha256:1a29730d366e996aaacffb2f1f1cb9593dc38e2ddd30c91250c6dde09ea9b417 \ - --hash=sha256:f38b2b640938a4f35ade69ac3d053042959b62a0f1076a5bbaa1b9526605a8a2 +inflection==0.5.1 # via drf-spectacular -isodate==0.7.2 \ - --hash=sha256:28009937d8031054830160fce6d409ed342816b543597cece116d966c6d99e15 \ - --hash=sha256:4cd1aa0f43ca76f4a6c6c0292a85f40b35ec2e43e315b59f06e6d32171a953e6 +isodate==0.7.2 # via python3-saml -itypes==1.2.0 \ - --hash=sha256:03da6872ca89d29aef62773672b2d408f490f80db48b23079a4b194c86dd04c6 \ - --hash=sha256:af886f129dea4a2a1e3d36595a2d139589e4dd287f5cab0b40e799ee81570ff1 +itypes==1.2.0 # via coreapi -jinja2==3.1.5 \ - --hash=sha256:8fefff8dc3034e27bb80d67c671eb8a9bc424c0ef4c0826edbff304cceff43bb \ - --hash=sha256:aba0f4dc9ed8013c424088f68a5c226f7d6097ed89b246d7749c2ec4175c6adb +jinja2==3.1.5 # via coreschema -jsonschema==4.23.0 \ - --hash=sha256:d71497fef26351a33265337fa77ffeb82423f3ea21283cd9467bb03999266bc4 \ - --hash=sha256:fbadb6f8b144a8f8cf9f0b89ba94501d143e50411a1278633f56a7acf7fd5566 +jsonschema==4.23.0 # via drf-spectacular -jsonschema-specifications==2024.10.1 \ - --hash=sha256:0f38b83639958ce1152d02a7f062902c41c8fd20d558b0c34344292d417ae272 \ - --hash=sha256:a09a0680616357d9a0ecf05c12ad234479f549239d0f5b55f3deea67475da9bf +jsonschema-specifications==2024.10.1 # via jsonschema -lxml==5.3.0 \ - --hash=sha256:01220dca0d066d1349bd6a1726856a78f7929f3878f7e2ee83c296c69495309e \ - --hash=sha256:02ced472497b8362c8e902ade23e3300479f4f43e45f4105c85ef43b8db85229 \ - --hash=sha256:052d99051e77a4f3e8482c65014cf6372e61b0a6f4fe9edb98503bb5364cfee3 \ - --hash=sha256:07da23d7ee08577760f0a71d67a861019103e4812c87e2fab26b039054594cc5 \ - --hash=sha256:094cb601ba9f55296774c2d57ad68730daa0b13dc260e1f941b4d13678239e70 \ - --hash=sha256:0a7056921edbdd7560746f4221dca89bb7a3fe457d3d74267995253f46343f15 \ - --hash=sha256:0c120f43553ec759f8de1fee2f4794452b0946773299d44c36bfe18e83caf002 \ - --hash=sha256:0d7b36afa46c97875303a94e8f3ad932bf78bace9e18e603f2085b652422edcd \ - --hash=sha256:0fdf3a3059611f7585a78ee10399a15566356116a4288380921a4b598d807a22 \ - --hash=sha256:109fa6fede314cc50eed29e6e56c540075e63d922455346f11e4d7a036d2b8cf \ - --hash=sha256:146173654d79eb1fc97498b4280c1d3e1e5d58c398fa530905c9ea50ea849b22 \ - --hash=sha256:1473427aff3d66a3fa2199004c3e601e6c4500ab86696edffdbc84954c72d832 \ - --hash=sha256:1483fd3358963cc5c1c9b122c80606a3a79ee0875bcac0204149fa09d6ff2727 \ - --hash=sha256:168f2dfcfdedf611eb285efac1516c8454c8c99caf271dccda8943576b67552e \ - --hash=sha256:17e8d968d04a37c50ad9c456a286b525d78c4a1c15dd53aa46c1d8e06bf6fa30 \ - --hash=sha256:18feb4b93302091b1541221196a2155aa296c363fd233814fa11e181adebc52f \ - --hash=sha256:1afe0a8c353746e610bd9031a630a95bcfb1a720684c3f2b36c4710a0a96528f \ - --hash=sha256:1d04f064bebdfef9240478f7a779e8c5dc32b8b7b0b2fc6a62e39b928d428e51 \ - --hash=sha256:1fdc9fae8dd4c763e8a31e7630afef517eab9f5d5d31a278df087f307bf601f4 \ - --hash=sha256:1ffc23010330c2ab67fac02781df60998ca8fe759e8efde6f8b756a20599c5de \ - --hash=sha256:20094fc3f21ea0a8669dc4c61ed7fa8263bd37d97d93b90f28fc613371e7a875 \ - --hash=sha256:213261f168c5e1d9b7535a67e68b1f59f92398dd17a56d934550837143f79c42 \ - --hash=sha256:218c1b2e17a710e363855594230f44060e2025b05c80d1f0661258142b2add2e \ - --hash=sha256:23e0553b8055600b3bf4a00b255ec5c92e1e4aebf8c2c09334f8368e8bd174d6 \ - --hash=sha256:25f1b69d41656b05885aa185f5fdf822cb01a586d1b32739633679699f220391 \ - --hash=sha256:2b3778cb38212f52fac9fe913017deea2fdf4eb1a4f8e4cfc6b009a13a6d3fcc \ - --hash=sha256:2bc9fd5ca4729af796f9f59cd8ff160fe06a474da40aca03fcc79655ddee1a8b \ - --hash=sha256:2c226a06ecb8cdef28845ae976da407917542c5e6e75dcac7cc33eb04aaeb237 \ - --hash=sha256:2c3406b63232fc7e9b8783ab0b765d7c59e7c59ff96759d8ef9632fca27c7ee4 \ - --hash=sha256:2c86bf781b12ba417f64f3422cfc302523ac9cd1d8ae8c0f92a1c66e56ef2e86 \ - --hash=sha256:2d9b8d9177afaef80c53c0a9e30fa252ff3036fb1c6494d427c066a4ce6a282f \ - --hash=sha256:2dec2d1130a9cda5b904696cec33b2cfb451304ba9081eeda7f90f724097300a \ - --hash=sha256:2dfab5fa6a28a0b60a20638dc48e6343c02ea9933e3279ccb132f555a62323d8 \ - --hash=sha256:2ecdd78ab768f844c7a1d4a03595038c166b609f6395e25af9b0f3f26ae1230f \ - --hash=sha256:315f9542011b2c4e1d280e4a20ddcca1761993dda3afc7a73b01235f8641e903 \ - --hash=sha256:36aef61a1678cb778097b4a6eeae96a69875d51d1e8f4d4b491ab3cfb54b5a03 \ - --hash=sha256:384aacddf2e5813a36495233b64cb96b1949da72bef933918ba5c84e06af8f0e \ - --hash=sha256:3879cc6ce938ff4eb4900d901ed63555c778731a96365e53fadb36437a131a99 \ - --hash=sha256:3c174dc350d3ec52deb77f2faf05c439331d6ed5e702fc247ccb4e6b62d884b7 \ - --hash=sha256:3eb44520c4724c2e1a57c0af33a379eee41792595023f367ba3952a2d96c2aab \ - --hash=sha256:406246b96d552e0503e17a1006fd27edac678b3fcc9f1be71a2f94b4ff61528d \ - --hash=sha256:41ce1f1e2c7755abfc7e759dc34d7d05fd221723ff822947132dc934d122fe22 \ - --hash=sha256:423b121f7e6fa514ba0c7918e56955a1d4470ed35faa03e3d9f0e3baa4c7e492 \ - --hash=sha256:44264ecae91b30e5633013fb66f6ddd05c006d3e0e884f75ce0b4755b3e3847b \ - --hash=sha256:482c2f67761868f0108b1743098640fbb2a28a8e15bf3f47ada9fa59d9fe08c3 \ - --hash=sha256:4b0c7a688944891086ba192e21c5229dea54382f4836a209ff8d0a660fac06be \ - --hash=sha256:4c1fefd7e3d00921c44dc9ca80a775af49698bbfd92ea84498e56acffd4c5469 \ - --hash=sha256:4e109ca30d1edec1ac60cdbe341905dc3b8f55b16855e03a54aaf59e51ec8c6f \ - --hash=sha256:501d0d7e26b4d261fca8132854d845e4988097611ba2531408ec91cf3fd9d20a \ - --hash=sha256:516f491c834eb320d6c843156440fe7fc0d50b33e44387fcec5b02f0bc118a4c \ - --hash=sha256:51806cfe0279e06ed8500ce19479d757db42a30fd509940b1701be9c86a5ff9a \ - --hash=sha256:562e7494778a69086f0312ec9689f6b6ac1c6b65670ed7d0267e49f57ffa08c4 \ - --hash=sha256:56b9861a71575f5795bde89256e7467ece3d339c9b43141dbdd54544566b3b94 \ - --hash=sha256:5b8f5db71b28b8c404956ddf79575ea77aa8b1538e8b2ef9ec877945b3f46442 \ - --hash=sha256:5c2fb570d7823c2bbaf8b419ba6e5662137f8166e364a8b2b91051a1fb40ab8b \ - --hash=sha256:5c54afdcbb0182d06836cc3d1be921e540be3ebdf8b8a51ee3ef987537455f84 \ - --hash=sha256:5d6a6972b93c426ace71e0be9a6f4b2cfae9b1baed2eed2006076a746692288c \ - --hash=sha256:609251a0ca4770e5a8768ff902aa02bf636339c5a93f9349b48eb1f606f7f3e9 \ - --hash=sha256:62d172f358f33a26d6b41b28c170c63886742f5b6772a42b59b4f0fa10526cb1 \ - --hash=sha256:62f7fdb0d1ed2065451f086519865b4c90aa19aed51081979ecd05a21eb4d1be \ - --hash=sha256:658f2aa69d31e09699705949b5fc4719cbecbd4a97f9656a232e7d6c7be1a367 \ - --hash=sha256:65ab5685d56914b9a2a34d67dd5488b83213d680b0c5d10b47f81da5a16b0b0e \ - --hash=sha256:68934b242c51eb02907c5b81d138cb977b2129a0a75a8f8b60b01cb8586c7b21 \ - --hash=sha256:68b87753c784d6acb8a25b05cb526c3406913c9d988d51f80adecc2b0775d6aa \ - --hash=sha256:69959bd3167b993e6e710b99051265654133a98f20cec1d9b493b931942e9c16 \ - --hash=sha256:6a7095eeec6f89111d03dabfe5883a1fd54da319c94e0fb104ee8f23616b572d \ - --hash=sha256:6b038cc86b285e4f9fea2ba5ee76e89f21ed1ea898e287dc277a25884f3a7dfe \ - --hash=sha256:6ba0d3dcac281aad8a0e5b14c7ed6f9fa89c8612b47939fc94f80b16e2e9bc83 \ - --hash=sha256:6e91cf736959057f7aac7adfc83481e03615a8e8dd5758aa1d95ea69e8931dba \ - --hash=sha256:6ee8c39582d2652dcd516d1b879451500f8db3fe3607ce45d7c5957ab2596040 \ - --hash=sha256:6f651ebd0b21ec65dfca93aa629610a0dbc13dbc13554f19b0113da2e61a4763 \ - --hash=sha256:71a8dd38fbd2f2319136d4ae855a7078c69c9a38ae06e0c17c73fd70fc6caad8 \ - --hash=sha256:74068c601baff6ff021c70f0935b0c7bc528baa8ea210c202e03757c68c5a4ff \ - --hash=sha256:7437237c6a66b7ca341e868cda48be24b8701862757426852c9b3186de1da8a2 \ - --hash=sha256:747a3d3e98e24597981ca0be0fd922aebd471fa99d0043a3842d00cdcad7ad6a \ - --hash=sha256:74bcb423462233bc5d6066e4e98b0264e7c1bed7541fff2f4e34fe6b21563c8b \ - --hash=sha256:78d9b952e07aed35fe2e1a7ad26e929595412db48535921c5013edc8aa4a35ce \ - --hash=sha256:7b1cd427cb0d5f7393c31b7496419da594fe600e6fdc4b105a54f82405e6626c \ - --hash=sha256:7d3d1ca42870cdb6d0d29939630dbe48fa511c203724820fc0fd507b2fb46577 \ - --hash=sha256:7e2f58095acc211eb9d8b5771bf04df9ff37d6b87618d1cbf85f92399c98dae8 \ - --hash=sha256:7f41026c1d64043a36fda21d64c5026762d53a77043e73e94b71f0521939cc71 \ - --hash=sha256:81b4e48da4c69313192d8c8d4311e5d818b8be1afe68ee20f6385d0e96fc9512 \ - --hash=sha256:86a6b24b19eaebc448dc56b87c4865527855145d851f9fc3891673ff97950540 \ - --hash=sha256:874a216bf6afaf97c263b56371434e47e2c652d215788396f60477540298218f \ - --hash=sha256:89e043f1d9d341c52bf2af6d02e6adde62e0a46e6755d5eb60dc6e4f0b8aeca2 \ - --hash=sha256:8c72e9563347c7395910de6a3100a4840a75a6f60e05af5e58566868d5eb2d6a \ - --hash=sha256:8dc2c0395bea8254d8daebc76dcf8eb3a95ec2a46fa6fae5eaccee366bfe02ce \ - --hash=sha256:8f0de2d390af441fe8b2c12626d103540b5d850d585b18fcada58d972b74a74e \ - --hash=sha256:92e67a0be1639c251d21e35fe74df6bcc40cba445c2cda7c4a967656733249e2 \ - --hash=sha256:94d6c3782907b5e40e21cadf94b13b0842ac421192f26b84c45f13f3c9d5dc27 \ - --hash=sha256:97acf1e1fd66ab53dacd2c35b319d7e548380c2e9e8c54525c6e76d21b1ae3b1 \ - --hash=sha256:9ada35dd21dc6c039259596b358caab6b13f4db4d4a7f8665764d616daf9cc1d \ - --hash=sha256:9c52100e2c2dbb0649b90467935c4b0de5528833c76a35ea1a2691ec9f1ee7a1 \ - --hash=sha256:9e41506fec7a7f9405b14aa2d5c8abbb4dbbd09d88f9496958b6d00cb4d45330 \ - --hash=sha256:9e4b47ac0f5e749cfc618efdf4726269441014ae1d5583e047b452a32e221920 \ - --hash=sha256:9fb81d2824dff4f2e297a276297e9031f46d2682cafc484f49de182aa5e5df99 \ - --hash=sha256:a0eabd0a81625049c5df745209dc7fcef6e2aea7793e5f003ba363610aa0a3ff \ - --hash=sha256:a3d819eb6f9b8677f57f9664265d0a10dd6551d227afb4af2b9cd7bdc2ccbf18 \ - --hash=sha256:a87de7dd873bf9a792bf1e58b1c3887b9264036629a5bf2d2e6579fe8e73edff \ - --hash=sha256:aa617107a410245b8660028a7483b68e7914304a6d4882b5ff3d2d3eb5948d8c \ - --hash=sha256:aac0bbd3e8dd2d9c45ceb82249e8bdd3ac99131a32b4d35c8af3cc9db1657179 \ - --hash=sha256:ab6dd83b970dc97c2d10bc71aa925b84788c7c05de30241b9e96f9b6d9ea3080 \ - --hash=sha256:ace2c2326a319a0bb8a8b0e5b570c764962e95818de9f259ce814ee666603f19 \ - --hash=sha256:ae5fe5c4b525aa82b8076c1a59d642c17b6e8739ecf852522c6321852178119d \ - --hash=sha256:b11a5d918a6216e521c715b02749240fb07ae5a1fefd4b7bf12f833bc8b4fe70 \ - --hash=sha256:b1c8c20847b9f34e98080da785bb2336ea982e7f913eed5809e5a3c872900f32 \ - --hash=sha256:b369d3db3c22ed14c75ccd5af429086f166a19627e84a8fdade3f8f31426e52a \ - --hash=sha256:b710bc2b8292966b23a6a0121f7a6c51d45d2347edcc75f016ac123b8054d3f2 \ - --hash=sha256:bd96517ef76c8654446fc3db9242d019a1bb5fe8b751ba414765d59f99210b79 \ - --hash=sha256:c00f323cc00576df6165cc9d21a4c21285fa6b9989c5c39830c3903dc4303ef3 \ - --hash=sha256:c162b216070f280fa7da844531169be0baf9ccb17263cf5a8bf876fcd3117fa5 \ - --hash=sha256:c1a69e58a6bb2de65902051d57fde951febad631a20a64572677a1052690482f \ - --hash=sha256:c1f794c02903c2824fccce5b20c339a1a14b114e83b306ff11b597c5f71a1c8d \ - --hash=sha256:c24037349665434f375645fa9d1f5304800cec574d0310f618490c871fd902b3 \ - --hash=sha256:c300306673aa0f3ed5ed9372b21867690a17dba38c68c44b287437c362ce486b \ - --hash=sha256:c56a1d43b2f9ee4786e4658c7903f05da35b923fb53c11025712562d5cc02753 \ - --hash=sha256:c6379f35350b655fd817cd0d6cbeef7f265f3ae5fedb1caae2eb442bbeae9ab9 \ - --hash=sha256:c802e1c2ed9f0c06a65bc4ed0189d000ada8049312cfeab6ca635e39c9608957 \ - --hash=sha256:cb83f8a875b3d9b458cada4f880fa498646874ba4011dc974e071a0a84a1b033 \ - --hash=sha256:cf120cce539453ae086eacc0130a324e7026113510efa83ab42ef3fcfccac7fb \ - --hash=sha256:dd36439be765e2dde7660212b5275641edbc813e7b24668831a5c8ac91180656 \ - --hash=sha256:dd5350b55f9fecddc51385463a4f67a5da829bc741e38cf689f38ec9023f54ab \ - --hash=sha256:df5c7333167b9674aa8ae1d4008fa4bc17a313cc490b2cca27838bbdcc6bb15b \ - --hash=sha256:e63601ad5cd8f860aa99d109889b5ac34de571c7ee902d6812d5d9ddcc77fa7d \ - --hash=sha256:e92ce66cd919d18d14b3856906a61d3f6b6a8500e0794142338da644260595cd \ - --hash=sha256:e99f5507401436fdcc85036a2e7dc2e28d962550afe1cbfc07c40e454256a859 \ - --hash=sha256:ea2e2f6f801696ad7de8aec061044d6c8c0dd4037608c7cab38a9a4d316bfb11 \ - --hash=sha256:eafa2c8658f4e560b098fe9fc54539f86528651f61849b22111a9b107d18910c \ - --hash=sha256:ecd4ad8453ac17bc7ba3868371bffb46f628161ad0eefbd0a855d2c8c32dd81a \ - --hash=sha256:ee70d08fd60c9565ba8190f41a46a54096afa0eeb8f76bd66f2c25d3b1b83005 \ - --hash=sha256:eec1bb8cdbba2925bedc887bc0609a80e599c75b12d87ae42ac23fd199445654 \ - --hash=sha256:ef0c1fe22171dd7c7c27147f2e9c3e86f8bdf473fed75f16b0c2e84a5030ce80 \ - --hash=sha256:f2901429da1e645ce548bf9171784c0f74f0718c3f6150ce166be39e4dd66c3e \ - --hash=sha256:f422a209d2455c56849442ae42f25dbaaba1c6c3f501d58761c619c7836642ec \ - --hash=sha256:f65e5120863c2b266dbcc927b306c5b78e502c71edf3295dfcb9501ec96e5fc7 \ - --hash=sha256:f7d4a670107d75dfe5ad080bed6c341d18c4442f9378c9f58e5851e86eb79965 \ - --hash=sha256:f914c03e6a31deb632e2daa881fe198461f4d06e57ac3d0e05bbcab8eae01945 \ - --hash=sha256:fb66442c2546446944437df74379e9cf9e9db353e61301d1a0e26482f43f0dd8 +lxml==5.3.0 # via # python3-saml # xmlsec -markdown==3.7 \ - --hash=sha256:2ae2471477cfd02dbbf038d5d9bc226d40def84b4fe2986e49b59b6b472bbed2 \ - --hash=sha256:7eb6df5690b81a1d7942992c97fad2938e956e79df20cbc6186e9c3a77b1c803 +markdown==3.7 # via django-markdownify -markupsafe==3.0.2 \ - --hash=sha256:0bff5e0ae4ef2e1ae4fdf2dfd5b76c75e5c2fa4132d05fc1b0dabcd20c7e28c4 \ - --hash=sha256:0f4ca02bea9a23221c0182836703cbf8930c5e9454bacce27e767509fa286a30 \ - --hash=sha256:1225beacc926f536dc82e45f8a4d68502949dc67eea90eab715dea3a21c1b5f0 \ - --hash=sha256:131a3c7689c85f5ad20f9f6fb1b866f402c445b220c19fe4308c0b147ccd2ad9 \ - --hash=sha256:15ab75ef81add55874e7ab7055e9c397312385bd9ced94920f2802310c930396 \ - --hash=sha256:1a9d3f5f0901fdec14d8d2f66ef7d035f2157240a433441719ac9a3fba440b13 \ - --hash=sha256:1c99d261bd2d5f6b59325c92c73df481e05e57f19837bdca8413b9eac4bd8028 \ - --hash=sha256:1e084f686b92e5b83186b07e8a17fc09e38fff551f3602b249881fec658d3eca \ - --hash=sha256:2181e67807fc2fa785d0592dc2d6206c019b9502410671cc905d132a92866557 \ - --hash=sha256:2cb8438c3cbb25e220c2ab33bb226559e7afb3baec11c4f218ffa7308603c832 \ - --hash=sha256:3169b1eefae027567d1ce6ee7cae382c57fe26e82775f460f0b2778beaad66c0 \ - --hash=sha256:3809ede931876f5b2ec92eef964286840ed3540dadf803dd570c3b7e13141a3b \ - --hash=sha256:38a9ef736c01fccdd6600705b09dc574584b89bea478200c5fbf112a6b0d5579 \ - --hash=sha256:3d79d162e7be8f996986c064d1c7c817f6df3a77fe3d6859f6f9e7be4b8c213a \ - --hash=sha256:444dcda765c8a838eaae23112db52f1efaf750daddb2d9ca300bcae1039adc5c \ - --hash=sha256:48032821bbdf20f5799ff537c7ac3d1fba0ba032cfc06194faffa8cda8b560ff \ - --hash=sha256:4aa4e5faecf353ed117801a068ebab7b7e09ffb6e1d5e412dc852e0da018126c \ - --hash=sha256:52305740fe773d09cffb16f8ed0427942901f00adedac82ec8b67752f58a1b22 \ - --hash=sha256:569511d3b58c8791ab4c2e1285575265991e6d8f8700c7be0e88f86cb0672094 \ - --hash=sha256:57cb5a3cf367aeb1d316576250f65edec5bb3be939e9247ae594b4bcbc317dfb \ - --hash=sha256:5b02fb34468b6aaa40dfc198d813a641e3a63b98c2b05a16b9f80b7ec314185e \ - --hash=sha256:6381026f158fdb7c72a168278597a5e3a5222e83ea18f543112b2662a9b699c5 \ - --hash=sha256:6af100e168aa82a50e186c82875a5893c5597a0c1ccdb0d8b40240b1f28b969a \ - --hash=sha256:6c89876f41da747c8d3677a2b540fb32ef5715f97b66eeb0c6b66f5e3ef6f59d \ - --hash=sha256:6e296a513ca3d94054c2c881cc913116e90fd030ad1c656b3869762b754f5f8a \ - --hash=sha256:70a87b411535ccad5ef2f1df5136506a10775d267e197e4cf531ced10537bd6b \ - --hash=sha256:7e94c425039cde14257288fd61dcfb01963e658efbc0ff54f5306b06054700f8 \ - --hash=sha256:846ade7b71e3536c4e56b386c2a47adf5741d2d8b94ec9dc3e92e5e1ee1e2225 \ - --hash=sha256:88416bd1e65dcea10bc7569faacb2c20ce071dd1f87539ca2ab364bf6231393c \ - --hash=sha256:88b49a3b9ff31e19998750c38e030fc7bb937398b1f78cfa599aaef92d693144 \ - --hash=sha256:8c4e8c3ce11e1f92f6536ff07154f9d49677ebaaafc32db9db4620bc11ed480f \ - --hash=sha256:8e06879fc22a25ca47312fbe7c8264eb0b662f6db27cb2d3bbbc74b1df4b9b87 \ - --hash=sha256:9025b4018f3a1314059769c7bf15441064b2207cb3f065e6ea1e7359cb46db9d \ - --hash=sha256:93335ca3812df2f366e80509ae119189886b0f3c2b81325d39efdb84a1e2ae93 \ - --hash=sha256:9778bd8ab0a994ebf6f84c2b949e65736d5575320a17ae8984a77fab08db94cf \ - --hash=sha256:9e2d922824181480953426608b81967de705c3cef4d1af983af849d7bd619158 \ - --hash=sha256:a123e330ef0853c6e822384873bef7507557d8e4a082961e1defa947aa59ba84 \ - --hash=sha256:a904af0a6162c73e3edcb969eeeb53a63ceeb5d8cf642fade7d39e7963a22ddb \ - --hash=sha256:ad10d3ded218f1039f11a75f8091880239651b52e9bb592ca27de44eed242a48 \ - --hash=sha256:b424c77b206d63d500bcb69fa55ed8d0e6a3774056bdc4839fc9298a7edca171 \ - --hash=sha256:b5a6b3ada725cea8a5e634536b1b01c30bcdcd7f9c6fff4151548d5bf6b3a36c \ - --hash=sha256:ba8062ed2cf21c07a9e295d5b8a2a5ce678b913b45fdf68c32d95d6c1291e0b6 \ - --hash=sha256:ba9527cdd4c926ed0760bc301f6728ef34d841f405abf9d4f959c478421e4efd \ - --hash=sha256:bbcb445fa71794da8f178f0f6d66789a28d7319071af7a496d4d507ed566270d \ - --hash=sha256:bcf3e58998965654fdaff38e58584d8937aa3096ab5354d493c77d1fdd66d7a1 \ - --hash=sha256:c0ef13eaeee5b615fb07c9a7dadb38eac06a0608b41570d8ade51c56539e509d \ - --hash=sha256:cabc348d87e913db6ab4aa100f01b08f481097838bdddf7c7a84b7575b7309ca \ - --hash=sha256:cdb82a876c47801bb54a690c5ae105a46b392ac6099881cdfb9f6e95e4014c6a \ - --hash=sha256:cfad01eed2c2e0c01fd0ecd2ef42c492f7f93902e39a42fc9ee1692961443a29 \ - --hash=sha256:d16a81a06776313e817c951135cf7340a3e91e8c1ff2fac444cfd75fffa04afe \ - --hash=sha256:d8213e09c917a951de9d09ecee036d5c7d36cb6cb7dbaece4c71a60d79fb9798 \ - --hash=sha256:e07c3764494e3776c602c1e78e298937c3315ccc9043ead7e685b7f2b8d47b3c \ - --hash=sha256:e17c96c14e19278594aa4841ec148115f9c7615a47382ecb6b82bd8fea3ab0c8 \ - --hash=sha256:e444a31f8db13eb18ada366ab3cf45fd4b31e4db1236a4448f68778c1d1a5a2f \ - --hash=sha256:e6a2a455bd412959b57a172ce6328d2dd1f01cb2135efda2e4576e8a23fa3b0f \ - --hash=sha256:eaa0a10b7f72326f1372a713e73c3f739b524b3af41feb43e4921cb529f5929a \ - --hash=sha256:eb7972a85c54febfb25b5c4b4f3af4dcc731994c7da0d8a0b4a6eb0640e1d178 \ - --hash=sha256:ee55d3edf80167e48ea11a923c7386f4669df67d7994554387f84e7d8b0a2bf0 \ - --hash=sha256:f3818cb119498c0678015754eba762e0d61e5b52d34c8b13d770f0719f7b1d79 \ - --hash=sha256:f8b3d067f2e40fe93e1ccdd6b2e1d16c43140e76f02fb1319a05cf2b79d99430 \ - --hash=sha256:fcabf5ff6eea076f859677f5f0b6b5c1a51e70a376b0579e0eadef8db48c6b50 +markupsafe==3.0.2 # via jinja2 -oauthlib==3.2.2 \ - --hash=sha256:8139f29aac13e25d502680e9e19963e83f16838d48a0d71c287fe40e7067fbca \ - --hash=sha256:9859c40929662bec5d64f34d01c99e093149682a3f38915dc0655d5a633dd918 +oauthlib==3.2.2 # via requests-oauthlib -openpyxl==3.1.5 \ - --hash=sha256:5282c12b107bffeef825f4617dc029afaf41d0ea60823bbb665ef3079dc79de2 \ - --hash=sha256:cf0e3cf56142039133628b5acffe8ef0c12bc902d2aadd3e0fe5878dc08d1050 +openpyxl==3.1.5 # via tablib -opentelemetry-api==1.29.0 \ - --hash=sha256:5fcd94c4141cc49c736271f3e1efb777bebe9cc535759c54c936cca4f1b312b8 \ - --hash=sha256:d04a6cf78aad09614f52964ecb38021e248f5714dc32c2e0d8fd99517b4d69cf +opentelemetry-api==1.29.0 # via # -r src/backend/requirements.in # opentelemetry-exporter-otlp-proto-grpc @@ -993,65 +213,41 @@ opentelemetry-api==1.29.0 \ # opentelemetry-instrumentation-wsgi # opentelemetry-sdk # opentelemetry-semantic-conventions -opentelemetry-exporter-otlp==1.29.0 \ - --hash=sha256:b8da6e20f5b0ffe604154b1e16a407eade17ce310c42fb85bb4e1246fc3688ad \ - --hash=sha256:ee7dfcccbb5e87ad9b389908452e10b7beeab55f70a83f41ce5b8c4efbde6544 +opentelemetry-exporter-otlp==1.29.0 # via -r src/backend/requirements.in -opentelemetry-exporter-otlp-proto-common==1.29.0 \ - --hash=sha256:a9d7376c06b4da9cf350677bcddb9618ed4b8255c3f6476975f5e38274ecd3aa \ - --hash=sha256:e7c39b5dbd1b78fe199e40ddfe477e6983cb61aa74ba836df09c3869a3e3e163 +opentelemetry-exporter-otlp-proto-common==1.29.0 # via # opentelemetry-exporter-otlp-proto-grpc # opentelemetry-exporter-otlp-proto-http -opentelemetry-exporter-otlp-proto-grpc==1.29.0 \ - --hash=sha256:3d324d07d64574d72ed178698de3d717f62a059a93b6b7685ee3e303384e73ea \ - --hash=sha256:5a2a3a741a2543ed162676cf3eefc2b4150e6f4f0a193187afb0d0e65039c69c +opentelemetry-exporter-otlp-proto-grpc==1.29.0 # via opentelemetry-exporter-otlp -opentelemetry-exporter-otlp-proto-http==1.29.0 \ - --hash=sha256:b10d174e3189716f49d386d66361fbcf6f2b9ad81e05404acdee3f65c8214204 \ - --hash=sha256:b228bdc0f0cfab82eeea834a7f0ffdd2a258b26aa33d89fb426c29e8e934d9d0 +opentelemetry-exporter-otlp-proto-http==1.29.0 # via opentelemetry-exporter-otlp -opentelemetry-instrumentation==0.50b0 \ - --hash=sha256:7d98af72de8dec5323e5202e46122e5f908592b22c6d24733aad619f07d82979 \ - --hash=sha256:b8f9fc8812de36e1c6dffa5bfc6224df258841fb387b6dfe5df15099daa10630 +opentelemetry-instrumentation==0.50b0 # via # opentelemetry-instrumentation-django # opentelemetry-instrumentation-redis # opentelemetry-instrumentation-requests # opentelemetry-instrumentation-wsgi -opentelemetry-instrumentation-django==0.50b0 \ - --hash=sha256:624fd0beb1ac827f2af31709c2da5cb55d8dc899c2449d6e8fcc9fa5538fd56b \ - --hash=sha256:ab7b4cd52b8f12420d968823f6bbfbc2a6ddb2af7a05fcb0d5b6755d338f1915 +opentelemetry-instrumentation-django==0.50b0 # via -r src/backend/requirements.in -opentelemetry-instrumentation-redis==0.50b0 \ - --hash=sha256:48c115189781a4eb1513457f4cb03f7c28bac45d4ca619802d0fec5d08db9e0f \ - --hash=sha256:ab5c983acdd2d4dd897b8d0f7c28d4fd548458259895830e43d9a206f4afa391 +opentelemetry-instrumentation-redis==0.50b0 # via -r src/backend/requirements.in -opentelemetry-instrumentation-requests==0.50b0 \ - --hash=sha256:2c60a890988d6765de9230004d0af9071b3b2e1ddba4ca3b631cfb8a1722208d \ - --hash=sha256:f8088c76f757985b492aad33331d21aec2f99c197472a57091c2e986a4b7ec8b +opentelemetry-instrumentation-requests==0.50b0 # via -r src/backend/requirements.in -opentelemetry-instrumentation-wsgi==0.50b0 \ - --hash=sha256:4bc0fdf52b603507d6170a25504f0ceea358d7e90a2c0e8794b7b7eca5ea355c \ - --hash=sha256:c25b5f1b664d984a41546a34cf2f893dcde6cf56922f88c475864e7df37edf4a +opentelemetry-instrumentation-wsgi==0.50b0 # via opentelemetry-instrumentation-django -opentelemetry-proto==1.29.0 \ - --hash=sha256:3c136aa293782e9b44978c738fff72877a4b78b5d21a64e879898db7b2d93e5d \ - --hash=sha256:495069c6f5495cbf732501cdcd3b7f60fda2b9d3d4255706ca99b7ca8dec53ff +opentelemetry-proto==1.29.0 # via # opentelemetry-exporter-otlp-proto-common # opentelemetry-exporter-otlp-proto-grpc # opentelemetry-exporter-otlp-proto-http -opentelemetry-sdk==1.29.0 \ - --hash=sha256:173be3b5d3f8f7d671f20ea37056710217959e774e2749d984355d1f9391a30a \ - --hash=sha256:b0787ce6aade6ab84315302e72bd7a7f2f014b0fb1b7c3295b88afe014ed0643 +opentelemetry-sdk==1.29.0 # via # -r src/backend/requirements.in # opentelemetry-exporter-otlp-proto-grpc # opentelemetry-exporter-otlp-proto-http -opentelemetry-semantic-conventions==0.50b0 \ - --hash=sha256:02dc6dbcb62f082de9b877ff19a3f1ffaa3c306300fa53bfac761c4567c83d38 \ - --hash=sha256:e87efba8fdb67fb38113efea6a349531e75ed7ffc01562f65b802fcecb5e115e +opentelemetry-semantic-conventions==0.50b0 # via # opentelemetry-instrumentation # opentelemetry-instrumentation-django @@ -1059,95 +255,18 @@ opentelemetry-semantic-conventions==0.50b0 \ # opentelemetry-instrumentation-requests # opentelemetry-instrumentation-wsgi # opentelemetry-sdk -opentelemetry-util-http==0.50b0 \ - --hash=sha256:21f8aedac861ffa3b850f8c0a6c373026189eb8630ac6e14a2bf8c55695cc090 \ - --hash=sha256:dc4606027e1bc02aabb9533cc330dd43f874fca492e4175c31d7154f341754af +opentelemetry-util-http==0.50b0 # via # opentelemetry-instrumentation-django # opentelemetry-instrumentation-requests # opentelemetry-instrumentation-wsgi -packaging==24.2 \ - --hash=sha256:09abb1bccd265c01f4a3aa3f7a7db064b36514d2cba19a2f694fe6150451a759 \ - --hash=sha256:c228a6dc5e932d346bc5739379109d49e8853dd8223571c7c5b55260edc0b97f +packaging==24.2 # via # gunicorn # opentelemetry-instrumentation -pdf2image==1.17.0 \ - --hash=sha256:eaa959bc116b420dd7ec415fcae49b98100dda3dd18cd2fdfa86d09f112f6d57 \ - --hash=sha256:ecdd58d7afb810dffe21ef2b1bbc057ef434dabbac6c33778a38a3f7744a27e2 +pdf2image==1.17.0 # via -r src/backend/requirements.in -pillow==11.1.0 \ - --hash=sha256:015c6e863faa4779251436db398ae75051469f7c903b043a48f078e437656f83 \ - --hash=sha256:0a2f91f8a8b367e7a57c6e91cd25af510168091fb89ec5146003e424e1558a96 \ - --hash=sha256:11633d58b6ee5733bde153a8dafd25e505ea3d32e261accd388827ee987baf65 \ - --hash=sha256:2062ffb1d36544d42fcaa277b069c88b01bb7298f4efa06731a7fd6cc290b81a \ - --hash=sha256:31eba6bbdd27dde97b0174ddf0297d7a9c3a507a8a1480e1e60ef914fe23d352 \ - --hash=sha256:3362c6ca227e65c54bf71a5f88b3d4565ff1bcbc63ae72c34b07bbb1cc59a43f \ - --hash=sha256:368da70808b36d73b4b390a8ffac11069f8a5c85f29eff1f1b01bcf3ef5b2a20 \ - --hash=sha256:36ba10b9cb413e7c7dfa3e189aba252deee0602c86c309799da5a74009ac7a1c \ - --hash=sha256:3764d53e09cdedd91bee65c2527815d315c6b90d7b8b79759cc48d7bf5d4f114 \ - --hash=sha256:3a5fe20a7b66e8135d7fd617b13272626a28278d0e578c98720d9ba4b2439d49 \ - --hash=sha256:3cdcdb0b896e981678eee140d882b70092dac83ac1cdf6b3a60e2216a73f2b91 \ - --hash=sha256:4637b88343166249fe8aa94e7c4a62a180c4b3898283bb5d3d2fd5fe10d8e4e0 \ - --hash=sha256:4db853948ce4e718f2fc775b75c37ba2efb6aaea41a1a5fc57f0af59eee774b2 \ - --hash=sha256:4dd43a78897793f60766563969442020e90eb7847463eca901e41ba186a7d4a5 \ - --hash=sha256:54251ef02a2309b5eec99d151ebf5c9904b77976c8abdcbce7891ed22df53884 \ - --hash=sha256:54ce1c9a16a9561b6d6d8cb30089ab1e5eb66918cb47d457bd996ef34182922e \ - --hash=sha256:593c5fd6be85da83656b93ffcccc2312d2d149d251e98588b14fbc288fd8909c \ - --hash=sha256:5bb94705aea800051a743aa4874bb1397d4695fb0583ba5e425ee0328757f196 \ - --hash=sha256:67cd427c68926108778a9005f2a04adbd5e67c442ed21d95389fe1d595458756 \ - --hash=sha256:70ca5ef3b3b1c4a0812b5c63c57c23b63e53bc38e758b37a951e5bc466449861 \ - --hash=sha256:73ddde795ee9b06257dac5ad42fcb07f3b9b813f8c1f7f870f402f4dc54b5269 \ - --hash=sha256:758e9d4ef15d3560214cddbc97b8ef3ef86ce04d62ddac17ad39ba87e89bd3b1 \ - --hash=sha256:7d33d2fae0e8b170b6a6c57400e077412240f6f5bb2a342cf1ee512a787942bb \ - --hash=sha256:7fdadc077553621911f27ce206ffcbec7d3f8d7b50e0da39f10997e8e2bb7f6a \ - --hash=sha256:8000376f139d4d38d6851eb149b321a52bb8893a88dae8ee7d95840431977081 \ - --hash=sha256:837060a8599b8f5d402e97197d4924f05a2e0d68756998345c829c33186217b1 \ - --hash=sha256:89dbdb3e6e9594d512780a5a1c42801879628b38e3efc7038094430844e271d8 \ - --hash=sha256:8c730dc3a83e5ac137fbc92dfcfe1511ce3b2b5d7578315b63dbbb76f7f51d90 \ - --hash=sha256:8e275ee4cb11c262bd108ab2081f750db2a1c0b8c12c1897f27b160c8bd57bbc \ - --hash=sha256:9044b5e4f7083f209c4e35aa5dd54b1dd5b112b108648f5c902ad586d4f945c5 \ - --hash=sha256:93a18841d09bcdd774dcdc308e4537e1f867b3dec059c131fde0327899734aa1 \ - --hash=sha256:9409c080586d1f683df3f184f20e36fb647f2e0bc3988094d4fd8c9f4eb1b3b3 \ - --hash=sha256:96f82000e12f23e4f29346e42702b6ed9a2f2fea34a740dd5ffffcc8c539eb35 \ - --hash=sha256:9aa9aeddeed452b2f616ff5507459e7bab436916ccb10961c4a382cd3e03f47f \ - --hash=sha256:9ee85f0696a17dd28fbcfceb59f9510aa71934b483d1f5601d1030c3c8304f3c \ - --hash=sha256:a07dba04c5e22824816b2615ad7a7484432d7f540e6fa86af60d2de57b0fcee2 \ - --hash=sha256:a3cd561ded2cf2bbae44d4605837221b987c216cff94f49dfeed63488bb228d2 \ - --hash=sha256:a697cd8ba0383bba3d2d3ada02b34ed268cb548b369943cd349007730c92bddf \ - --hash=sha256:a76da0a31da6fcae4210aa94fd779c65c75786bc9af06289cd1c184451ef7a65 \ - --hash=sha256:a85b653980faad27e88b141348707ceeef8a1186f75ecc600c395dcac19f385b \ - --hash=sha256:a8d65b38173085f24bc07f8b6c505cbb7418009fa1a1fcb111b1f4961814a442 \ - --hash=sha256:aa8dd43daa836b9a8128dbe7d923423e5ad86f50a7a14dc688194b7be5c0dea2 \ - --hash=sha256:ab8a209b8485d3db694fa97a896d96dd6533d63c22829043fd9de627060beade \ - --hash=sha256:abc56501c3fd148d60659aae0af6ddc149660469082859fa7b066a298bde9482 \ - --hash=sha256:ad5db5781c774ab9a9b2c4302bbf0c1014960a0a7be63278d13ae6fdf88126fe \ - --hash=sha256:ae98e14432d458fc3de11a77ccb3ae65ddce70f730e7c76140653048c71bfcbc \ - --hash=sha256:b20be51b37a75cc54c2c55def3fa2c65bb94ba859dde241cd0a4fd302de5ae0a \ - --hash=sha256:b523466b1a31d0dcef7c5be1f20b942919b62fd6e9a9be199d035509cbefc0ec \ - --hash=sha256:b5d658fbd9f0d6eea113aea286b21d3cd4d3fd978157cbf2447a6035916506d3 \ - --hash=sha256:b6123aa4a59d75f06e9dd3dac5bf8bc9aa383121bb3dd9a7a612e05eabc9961a \ - --hash=sha256:bd165131fd51697e22421d0e467997ad31621b74bfc0b75956608cb2906dda07 \ - --hash=sha256:bf902d7413c82a1bfa08b06a070876132a5ae6b2388e2712aab3a7cbc02205c6 \ - --hash=sha256:c12fc111ef090845de2bb15009372175d76ac99969bdf31e2ce9b42e4b8cd88f \ - --hash=sha256:c1eec9d950b6fe688edee07138993e54ee4ae634c51443cfb7c1e7613322718e \ - --hash=sha256:c640e5a06869c75994624551f45e5506e4256562ead981cce820d5ab39ae2192 \ - --hash=sha256:cc1331b6d5a6e144aeb5e626f4375f5b7ae9934ba620c0ac6b3e43d5e683a0f0 \ - --hash=sha256:cfd5cd998c2e36a862d0e27b2df63237e67273f2fc78f47445b14e73a810e7e6 \ - --hash=sha256:d3d8da4a631471dfaf94c10c85f5277b1f8e42ac42bade1ac67da4b4a7359b73 \ - --hash=sha256:d44ff19eea13ae4acdaaab0179fa68c0c6f2f45d66a4d8ec1eda7d6cecbcc15f \ - --hash=sha256:dd0052e9db3474df30433f83a71b9b23bd9e4ef1de13d92df21a52c0303b8ab6 \ - --hash=sha256:dd0e081319328928531df7a0e63621caf67652c8464303fd102141b785ef9547 \ - --hash=sha256:dda60aa465b861324e65a78c9f5cf0f4bc713e4309f83bc387be158b077963d9 \ - --hash=sha256:e06695e0326d05b06833b40b7ef477e475d0b1ba3a6d27da1bb48c23209bf457 \ - --hash=sha256:e1abe69aca89514737465752b4bcaf8016de61b3be1397a8fc260ba33321b3a8 \ - --hash=sha256:e267b0ed063341f3e60acd25c05200df4193e15a4a5807075cd71225a2386e26 \ - --hash=sha256:e5449ca63da169a2e6068dd0e2fcc8d91f9558aba89ff6d02121ca8ab11e79e5 \ - --hash=sha256:e63e4e5081de46517099dc30abe418122f54531a6ae2ebc8680bcd7096860eab \ - --hash=sha256:f189805c8be5ca5add39e6f899e6ce2ed824e65fb45f3c28cb2841911da19070 \ - --hash=sha256:f7955ecf5609dee9442cbface754f2c6e541d9e6eda87fad7f7a989b0bdb9d71 \ - --hash=sha256:f86d3a7a9af5d826744fabf4afd15b9dfef44fe69a98541f666f66fbb8d3fef9 \ - --hash=sha256:fbd43429d0d7ed6533b25fc993861b8fd512c42d04514a0dd6337fb3ccf22761 +pillow==11.1.0 # via # -r src/backend/requirements.in # django-stdimage @@ -1155,464 +274,113 @@ pillow==11.1.0 \ # python-barcode # qrcode # weasyprint -pint==0.24.4 \ - --hash=sha256:35275439b574837a6cd3020a5a4a73645eb125ce4152a73a2f126bf164b91b80 \ - --hash=sha256:aa54926c8772159fcf65f82cc0d34de6768c151b32ad1deb0331291c38fe7659 +pint==0.24.4 # via -r src/backend/requirements.in -pip-licenses==5.0.0 \ - --hash=sha256:0633a1f9aab58e5a6216931b0e1d5cdded8bcc2709ff563674eb0e2ff9e77e8e \ - --hash=sha256:82c83666753efb86d1af1c405c8ab273413eb10d6689c218df2f09acf40e477d +pip-licenses==5.0.0 # via -r src/backend/requirements.in -platformdirs==4.3.6 \ - --hash=sha256:357fb2acbc885b0419afd3ce3ed34564c13c9b95c89360cd9563f73aa5e2b907 \ - --hash=sha256:73e575e1408ab8103900836b97580d5307456908a03e92031bab39e4554cc3fb +platformdirs==4.3.6 # via pint -ppf-datamatrix==0.2 \ - --hash=sha256:819be65eae444b760e178d5761853f78f8e5fca14fec2809b5e3369978fa9244 \ - --hash=sha256:8f034d9c90e408f60f8b10a273baab81014c9a81c983dc1ebdc31d4ca5ac5582 +ppf-datamatrix==0.2 # via -r src/backend/requirements.in -prettytable==3.12.0 \ - --hash=sha256:77ca0ad1c435b6e363d7e8623d7cc4fcf2cf15513bf77a1c1b2e814930ac57cc \ - --hash=sha256:f04b3e1ba35747ac86e96ec33e3bb9748ce08e254dc2a1c6253945901beec804 +prettytable==3.12.0 # via pip-licenses -protobuf==5.29.3 \ - --hash=sha256:0a18ed4a24198528f2333802eb075e59dea9d679ab7a6c5efb017a59004d849f \ - --hash=sha256:0eb32bfa5219fc8d4111803e9a690658aa2e6366384fd0851064b963b6d1f2a7 \ - --hash=sha256:3ea51771449e1035f26069c4c7fd51fba990d07bc55ba80701c78f886bf9c888 \ - --hash=sha256:5da0f41edaf117bde316404bad1a486cb4ededf8e4a54891296f648e8e076620 \ - --hash=sha256:6ce8cc3389a20693bfde6c6562e03474c40851b44975c9b2bf6df7d8c4f864da \ - --hash=sha256:84a57163a0ccef3f96e4b6a20516cedcf5bb3a95a657131c5c3ac62200d23252 \ - --hash=sha256:a4fa6f80816a9a0678429e84973f2f98cbc218cca434abe8db2ad0bffc98503a \ - --hash=sha256:a8434404bbf139aa9e1300dbf989667a83d42ddda9153d8ab76e0d5dcaca484e \ - --hash=sha256:b89c115d877892a512f79a8114564fb435943b59067615894c3b13cd3e1fa107 \ - --hash=sha256:c027e08a08be10b67c06bf2370b99c811c466398c357e615ca88c91c07f0910f \ - --hash=sha256:daaf63f70f25e8689c072cfad4334ca0ac1d1e05a92fc15c54eb9cf23c3efd84 +protobuf==5.29.3 # via # googleapis-common-protos # opentelemetry-proto -py-moneyed==3.0 \ - --hash=sha256:4906f0f02cf2b91edba2e156f2d4e9a78f224059ab8c8fa2ff26230c75d894e8 \ - --hash=sha256:9583a14f99c05b46196193d8185206e9b73c8439fc8a5eee9cfc7e733676d9bb +py-moneyed==3.0 # via django-money -pycparser==2.22 \ - --hash=sha256:491c8be9c040f5390f5bf44a5b07752bd07f56edf992381b05c701439eec10f6 \ - --hash=sha256:c3702b6d3dd8c7abc1afa565d7e63d53a1d0bd86cdc24edd75470f4de499cfcc +pycparser==2.22 # via cffi -pydyf==0.10.0 \ - --hash=sha256:357194593efaf61d7b48ab97c3d59722114934967c3df3d7878ca6dd25b04c30 \ - --hash=sha256:ef76b6c0976a091a9e15827fb5800e5e37e7cd1a3ca4d4bd19d10a14ea8c0ae3 +pydyf==0.10.0 # via # -r src/backend/requirements.in # weasyprint -pyjwt[crypto]==2.10.1 \ - --hash=sha256:3cc5772eb20009233caf06e9d8a0577824723b44e6648ee0a2aedb6cf9381953 \ - --hash=sha256:dcdd193e30abefd5debf142f9adfcdd2b58004e644f25406ffaebd50bd98dacb +pyjwt[crypto]==2.10.1 # via # django-allauth # djangorestframework-simplejwt -pyphen==0.17.0 \ - --hash=sha256:1d13acd1ce37a384d7612954ae6c7801bb4c5316da0e2b937b2127ba702a3da4 \ - --hash=sha256:dad0b2e4aa80f6d70bf06711b2da36c47a756b933c1d0c4cbbab40f643a5958c +pyphen==0.17.0 # via weasyprint -python-barcode[images]==0.15.1 \ - --hash=sha256:057636fba37369c22852410c8535b36adfbeb965ddfd4e5b6924455d692e0886 \ - --hash=sha256:3b1825fbdb11e597466dff4286b4ea9b1e86a57717b59e563ae679726fc854de +python-barcode[images]==0.15.1 # via -r src/backend/requirements.in -python-dateutil==2.9.0.post0 \ - --hash=sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3 \ - --hash=sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427 +python-dateutil==2.9.0.post0 # via # django-recurrence # icalendar -python-dotenv==1.0.1 \ - --hash=sha256:e324ee90a023d808f1959c46bcbc04446a10ced277783dc6ee09987c37ec10ca \ - --hash=sha256:f7b63ef50f1b690dddf550d03497b66d609393b40b564ed0d674909a68ebf16a +python-dotenv==1.0.1 # via -r src/backend/requirements.in -python-fsutil==0.14.1 \ - --hash=sha256:0d45e623f0f4403f674bdd8ae7aa7d24a4b3132ea45c65416bd2865e6b20b035 \ - --hash=sha256:8fb204fa8059f37bdeee8a1dc0fff010170202ea47c4225ee71bb3c26f3997be +python-fsutil==0.14.1 # via django-maintenance-mode -python-ipware==3.0.0 \ - --hash=sha256:9117b1c4dddcb5d5ca49e6a9617de2fc66aec2ef35394563ac4eecabdf58c062 \ - --hash=sha256:fc936e6e7ec9fcc107f9315df40658f468ac72f739482a707181742882e36b60 +python-ipware==3.0.0 # via django-ipware -python3-openid==3.2.0 \ - --hash=sha256:33fbf6928f401e0b790151ed2b5290b02545e8775f982485205a066f874aaeaf \ - --hash=sha256:6626f771e0417486701e0b4daff762e7212e820ca5b29fcc0d05f6f8736dfa6b +python3-openid==3.2.0 # via django-allauth -python3-saml==1.16.0 \ - --hash=sha256:20b97d11b04f01ee22e98f4a38242e2fea2e28fbc7fbc9bdd57cab5ac7fc2d0d \ - --hash=sha256:97c9669aecabc283c6e5fb4eb264f446b6e006f5267d01c9734f9d8bffdac133 \ - --hash=sha256:c49097863c278ff669a337a96c46dc1f25d16307b4bb2679d2d1733cc4f5176a +python3-saml==1.16.0 # via django-allauth -pytz==2024.2 \ - --hash=sha256:2aa355083c50a0f93fa581709deac0c9ad65cca8a9e9beac660adcbd493c798a \ - --hash=sha256:31c7c1817eb7fae7ca4b8c7ee50c72f93aa2dd863de768e1ef4245d426aa0725 +pytz==2024.2 # via # django-dbbackup # djangorestframework -pyyaml==6.0.2 \ - --hash=sha256:01179a4a8559ab5de078078f37e5c1a30d76bb88519906844fd7bdea1b7729ff \ - --hash=sha256:0833f8694549e586547b576dcfaba4a6b55b9e96098b36cdc7ebefe667dfed48 \ - --hash=sha256:0a9a2848a5b7feac301353437eb7d5957887edbf81d56e903999a75a3d743086 \ - --hash=sha256:0b69e4ce7a131fe56b7e4d770c67429700908fc0752af059838b1cfb41960e4e \ - --hash=sha256:0ffe8360bab4910ef1b9e87fb812d8bc0a308b0d0eef8c8f44e0254ab3b07133 \ - --hash=sha256:11d8f3dd2b9c1207dcaf2ee0bbbfd5991f571186ec9cc78427ba5bd32afae4b5 \ - --hash=sha256:17e311b6c678207928d649faa7cb0d7b4c26a0ba73d41e99c4fff6b6c3276484 \ - --hash=sha256:1e2120ef853f59c7419231f3bf4e7021f1b936f6ebd222406c3b60212205d2ee \ - --hash=sha256:1f71ea527786de97d1a0cc0eacd1defc0985dcf6b3f17bb77dcfc8c34bec4dc5 \ - --hash=sha256:23502f431948090f597378482b4812b0caae32c22213aecf3b55325e049a6c68 \ - --hash=sha256:24471b829b3bf607e04e88d79542a9d48bb037c2267d7927a874e6c205ca7e9a \ - --hash=sha256:29717114e51c84ddfba879543fb232a6ed60086602313ca38cce623c1d62cfbf \ - --hash=sha256:2e99c6826ffa974fe6e27cdb5ed0021786b03fc98e5ee3c5bfe1fd5015f42b99 \ - --hash=sha256:39693e1f8320ae4f43943590b49779ffb98acb81f788220ea932a6b6c51004d8 \ - --hash=sha256:3ad2a3decf9aaba3d29c8f537ac4b243e36bef957511b4766cb0057d32b0be85 \ - --hash=sha256:3b1fdb9dc17f5a7677423d508ab4f243a726dea51fa5e70992e59a7411c89d19 \ - --hash=sha256:41e4e3953a79407c794916fa277a82531dd93aad34e29c2a514c2c0c5fe971cc \ - --hash=sha256:43fa96a3ca0d6b1812e01ced1044a003533c47f6ee8aca31724f78e93ccc089a \ - --hash=sha256:50187695423ffe49e2deacb8cd10510bc361faac997de9efef88badc3bb9e2d1 \ - --hash=sha256:5ac9328ec4831237bec75defaf839f7d4564be1e6b25ac710bd1a96321cc8317 \ - --hash=sha256:5d225db5a45f21e78dd9358e58a98702a0302f2659a3c6cd320564b75b86f47c \ - --hash=sha256:6395c297d42274772abc367baaa79683958044e5d3835486c16da75d2a694631 \ - --hash=sha256:688ba32a1cffef67fd2e9398a2efebaea461578b0923624778664cc1c914db5d \ - --hash=sha256:68ccc6023a3400877818152ad9a1033e3db8625d899c72eacb5a668902e4d652 \ - --hash=sha256:70b189594dbe54f75ab3a1acec5f1e3faa7e8cf2f1e08d9b561cb41b845f69d5 \ - --hash=sha256:797b4f722ffa07cc8d62053e4cff1486fa6dc094105d13fea7b1de7d8bf71c9e \ - --hash=sha256:7c36280e6fb8385e520936c3cb3b8042851904eba0e58d277dca80a5cfed590b \ - --hash=sha256:7e7401d0de89a9a855c839bc697c079a4af81cf878373abd7dc625847d25cbd8 \ - --hash=sha256:80bab7bfc629882493af4aa31a4cfa43a4c57c83813253626916b8c7ada83476 \ - --hash=sha256:82d09873e40955485746739bcb8b4586983670466c23382c19cffecbf1fd8706 \ - --hash=sha256:8388ee1976c416731879ac16da0aff3f63b286ffdd57cdeb95f3f2e085687563 \ - --hash=sha256:8824b5a04a04a047e72eea5cec3bc266db09e35de6bdfe34c9436ac5ee27d237 \ - --hash=sha256:8b9c7197f7cb2738065c481a0461e50ad02f18c78cd75775628afb4d7137fb3b \ - --hash=sha256:9056c1ecd25795207ad294bcf39f2db3d845767be0ea6e6a34d856f006006083 \ - --hash=sha256:936d68689298c36b53b29f23c6dbb74de12b4ac12ca6cfe0e047bedceea56180 \ - --hash=sha256:9b22676e8097e9e22e36d6b7bda33190d0d400f345f23d4065d48f4ca7ae0425 \ - --hash=sha256:a4d3091415f010369ae4ed1fc6b79def9416358877534caf6a0fdd2146c87a3e \ - --hash=sha256:a8786accb172bd8afb8be14490a16625cbc387036876ab6ba70912730faf8e1f \ - --hash=sha256:a9f8c2e67970f13b16084e04f134610fd1d374bf477b17ec1599185cf611d725 \ - --hash=sha256:bc2fa7c6b47d6bc618dd7fb02ef6fdedb1090ec036abab80d4681424b84c1183 \ - --hash=sha256:c70c95198c015b85feafc136515252a261a84561b7b1d51e3384e0655ddf25ab \ - --hash=sha256:cc1c1159b3d456576af7a3e4d1ba7e6924cb39de8f67111c735f6fc832082774 \ - --hash=sha256:ce826d6ef20b1bc864f0a68340c8b3287705cae2f8b4b1d932177dcc76721725 \ - --hash=sha256:d584d9ec91ad65861cc08d42e834324ef890a082e591037abe114850ff7bbc3e \ - --hash=sha256:d7fded462629cfa4b685c5416b949ebad6cec74af5e2d42905d41e257e0869f5 \ - --hash=sha256:d84a1718ee396f54f3a086ea0a66d8e552b2ab2017ef8b420e92edbc841c352d \ - --hash=sha256:d8e03406cac8513435335dbab54c0d385e4a49e4945d2909a581c83647ca0290 \ - --hash=sha256:e10ce637b18caea04431ce14fabcf5c64a1c61ec9c56b071a4b7ca131ca52d44 \ - --hash=sha256:ec031d5d2feb36d1d1a24380e4db6d43695f3748343d99434e6f5f9156aaa2ed \ - --hash=sha256:ef6107725bd54b262d6dedcc2af448a266975032bc85ef0172c5f059da6325b4 \ - --hash=sha256:efdca5630322a10774e8e98e1af481aad470dd62c3170801852d752aa7a783ba \ - --hash=sha256:f753120cb8181e736c57ef7636e83f31b9c0d1722c516f7e86cf15b7aa57ff12 \ - --hash=sha256:ff3824dc5261f50c9b0dfb3be22b4567a6f938ccce4587b38952d85fd9e9afe4 +pyyaml==6.0.2 # via # -r src/backend/requirements.in # drf-spectacular # tablib -qrcode[pil]==8.0 \ - --hash=sha256:025ce2b150f7fe4296d116ee9bad455a6643ab4f6e7dce541613a4758cbce347 \ - --hash=sha256:9fc05f03305ad27a709eb742cf3097fa19e6f6f93bb9e2f039c0979190f6f1b1 +qrcode[pil]==8.0 # via # -r src/backend/requirements.in # django-allauth -rapidfuzz==3.11.0 \ - --hash=sha256:0b488b244931d0291412917e6e46ee9f6a14376625e150056fe7c4426ef28225 \ - --hash=sha256:1315cd2a351144572e31fe3df68340d4b83ddec0af8b2e207cd32930c6acd037 \ - --hash=sha256:1bac4873f6186f5233b0084b266bfb459e997f4c21fc9f029918f44a9eccd304 \ - --hash=sha256:1cb1965a28b0fa64abdee130c788a0bc0bb3cf9ef7e3a70bf055c086c14a3d7e \ - --hash=sha256:22033677982b9c4c49676f215b794b0404073f8974f98739cb7234e4a9ade9ad \ - --hash=sha256:231c8b2efbd7f8d2ecd1ae900363ba168b8870644bb8f2b5aa96e4a7573bde19 \ - --hash=sha256:25398d9ac7294e99876a3027ffc52c6bebeb2d702b1895af6ae9c541ee676702 \ - --hash=sha256:2c87319b0ab9d269ab84f6453601fd49b35d9e4a601bbaef43743f26fabf496c \ - --hash=sha256:3048c6ed29d693fba7d2a7caf165f5e0bb2b9743a0989012a98a47b975355cca \ - --hash=sha256:339607394941801e6e3f6c1ecd413a36e18454e7136ed1161388de674f47f9d9 \ - --hash=sha256:3794df87313dfb56fafd679b962e0613c88a293fd9bd5dd5c2793d66bf06a101 \ - --hash=sha256:3857e335f97058c4b46fa39ca831290b70de554a5c5af0323d2f163b19c5f2a6 \ - --hash=sha256:3871fa7dfcef00bad3c7e8ae8d8fd58089bad6fb21f608d2bf42832267ca9663 \ - --hash=sha256:3f28952da055dbfe75828891cd3c9abf0984edc8640573c18b48c14c68ca5e06 \ - --hash=sha256:42f4dd264ada7a9aa0805ea0da776dc063533917773cf2df5217f14eb4429eae \ - --hash=sha256:4416ca69af933d4a8ad30910149d3db6d084781d5c5fdedb713205389f535385 \ - --hash=sha256:4469307f464ae3089acf3210b8fc279110d26d10f79e576f385a98f4429f7d97 \ - --hash=sha256:4513dd01cee11e354c31b75f652d4d466c9440b6859f84e600bdebfccb17735a \ - --hash=sha256:45b15b8a118856ac9caac6877f70f38b8a0d310475d50bc814698659eabc1cdb \ - --hash=sha256:494eef2c68305ab75139034ea25328a04a548d297712d9cf887bf27c158c388b \ - --hash=sha256:4d0d26c7172bdb64f86ee0765c5b26ea1dc45c52389175888ec073b9b28f4305 \ - --hash=sha256:4f9f12c2d0aa52b86206d2059916153876a9b1cf9dfb3cf2f344913167f1c3d4 \ - --hash=sha256:51f24cb39e64256221e6952f22545b8ce21cacd59c0d3e367225da8fc4b868d8 \ - --hash=sha256:54e7f442fb9cca81e9df32333fb075ef729052bcabe05b0afc0441f462299114 \ - --hash=sha256:5a167344c1d6db06915fb0225592afdc24d8bafaaf02de07d4788ddd37f4bc2f \ - --hash=sha256:5b659e1e2ea2784a9a397075a7fc395bfa4fe66424042161c4bcaf6e4f637b38 \ - --hash=sha256:5bb636b0150daa6d3331b738f7c0f8b25eadc47f04a40e5c23c4bfb4c4e20ae3 \ - --hash=sha256:5e8ea35f2419c7d56b3e75fbde2698766daedb374f20eea28ac9b1f668ef4f74 \ - --hash=sha256:5e8f93bc736020351a6f8e71666e1f486bb8bd5ce8112c443a30c77bfde0eb68 \ - --hash=sha256:62171b270ecc4071be1c1f99960317db261d4c8c83c169e7f8ad119211fe7397 \ - --hash=sha256:6668321f90aa02a5a789d4e16058f2e4f2692c5230252425c3532a8a62bc3424 \ - --hash=sha256:6ad02bab756751c90fa27f3069d7b12146613061341459abf55f8190d899649f \ - --hash=sha256:6b01c1ddbb054283797967ddc5433d5c108d680e8fa2684cf368be05407b07e4 \ - --hash=sha256:714a7ba31ba46b64d30fccfe95f8013ea41a2e6237ba11a805a27cdd3bce2573 \ - --hash=sha256:76a4a11ba8f678c9e5876a7d465ab86def047a4fcc043617578368755d63a1bc \ - --hash=sha256:7864e80a0d4e23eb6194254a81ee1216abdc53f9dc85b7f4d56668eced022eb8 \ - --hash=sha256:82497f244aac10b20710448645f347d862364cc4f7d8b9ba14bd66b5ce4dec18 \ - --hash=sha256:84819390a36d6166cec706b9d8f0941f115f700b7faecab5a7e22fc367408bc3 \ - --hash=sha256:8724a978f8af7059c5323d523870bf272a097478e1471295511cf58b2642ff83 \ - --hash=sha256:8b63cb1f2eb371ef20fb155e95efd96e060147bdd4ab9fc400c97325dfee9fe1 \ - --hash=sha256:8c7af25bda96ac799378ac8aba54a8ece732835c7b74cfc201b688a87ed11152 \ - --hash=sha256:8dd501de6f7a8f83557d20613b58734d1cb5f0be78d794cde64fe43cfc63f5f2 \ - --hash=sha256:8ed59044aea9eb6c663112170f2399b040d5d7b162828b141f2673e822093fa8 \ - --hash=sha256:906f1f2a1b91c06599b3dd1be207449c5d4fc7bd1e1fa2f6aef161ea6223f165 \ - --hash=sha256:92ebb7c12f682b5906ed98429f48a3dd80dd0f9721de30c97a01473d1a346576 \ - --hash=sha256:99aebef8268f2bc0b445b5640fd3312e080bd17efd3fbae4486b20ac00466308 \ - --hash=sha256:9a1b3ebc62d4bcdfdeba110944a25ab40916d5383c5e57e7c4a8dc0b6c17211a \ - --hash=sha256:9a52eea839e4bdc72c5e60a444d26004da00bb5bc6301e99b3dde18212e41465 \ - --hash=sha256:9c6d7fea39cb33e71de86397d38bf7ff1a6273e40367f31d05761662ffda49e4 \ - --hash=sha256:a53ca4d3f52f00b393fab9b5913c5bafb9afc27d030c8a1db1283da6917a860f \ - --hash=sha256:a7743cca45b4684c54407e8638f6d07b910d8d811347b9d42ff21262c7c23245 \ - --hash=sha256:aaf391fb6715866bc14681c76dc0308f46877f7c06f61d62cc993b79fc3c4a2a \ - --hash=sha256:ab9eab33ee3213f7751dc07a1a61b8d9a3d748ca4458fffddd9defa6f0493c16 \ - --hash=sha256:b04f29735bad9f06bb731c214f27253bd8bedb248ef9b8a1b4c5bde65b838454 \ - --hash=sha256:b1472986fd9c5d318399a01a0881f4a0bf4950264131bb8e2deba9df6d8c362b \ - --hash=sha256:b1d67d67f89e4e013a5295e7523bc34a7a96f2dba5dd812c7c8cb65d113cbf28 \ - --hash=sha256:b1f7efdd7b7adb32102c2fa481ad6f11923e2deb191f651274be559d56fc913b \ - --hash=sha256:b2669eafee38c5884a6e7cc9769d25c19428549dcdf57de8541cf9e82822e7db \ - --hash=sha256:ba26d87fe7fcb56c4a53b549a9e0e9143f6b0df56d35fe6ad800c902447acd5b \ - --hash=sha256:be15496e7244361ff0efcd86e52559bacda9cd975eccf19426a0025f9547c792 \ - --hash=sha256:c36539ed2c0173b053dafb221458812e178cfa3224ade0960599bec194637048 \ - --hash=sha256:c408f09649cbff8da76f8d3ad878b64ba7f7abdad1471efb293d2c075e80c822 \ - --hash=sha256:cd340bbd025302276b5aa221dccfe43040c7babfc32f107c36ad783f2ffd8775 \ - --hash=sha256:d0edecc3f90c2653298d380f6ea73b536944b767520c2179ec5d40b9145e47aa \ - --hash=sha256:d2a0f7e17f33e7890257367a1662b05fecaf56625f7dbb6446227aaa2b86448b \ - --hash=sha256:d71da0012face6f45432a11bc59af19e62fac5a41f8ce489e80c0add8153c3d1 \ - --hash=sha256:d895998fec712544c13cfe833890e0226585cf0391dd3948412441d5d68a2b8c \ - --hash=sha256:d95f9e9f3777b96241d8a00d6377cc9c716981d828b5091082d0fe3a2924b43e \ - --hash=sha256:d9727b85511b912571a76ce53c7640ba2c44c364e71cef6d7359b5412739c570 \ - --hash=sha256:d98a46cf07c0c875d27e8a7ed50f304d83063e49b9ab63f21c19c154b4c0d08d \ - --hash=sha256:d994cf27e2f874069884d9bddf0864f9b90ad201fcc9cb2f5b82bacc17c8d5f2 \ - --hash=sha256:dc0e0d41ad8a056a9886bac91ff9d9978e54a244deb61c2972cc76b66752de9c \ - --hash=sha256:dfaefe08af2a928e72344c800dcbaf6508e86a4ed481e28355e8d4b6a6a5230e \ - --hash=sha256:e60814edd0c9b511b5f377d48b9782b88cfe8be07a98f99973669299c8bb318a \ - --hash=sha256:eb8a54543d16ab1b69e2c5ed96cabbff16db044a50eddfc028000138ca9ddf33 \ - --hash=sha256:eb97c53112b593f89a90b4f6218635a9d1eea1d7f9521a3b7d24864228bbc0aa \ - --hash=sha256:ebadd5b8624d8ad503e505a99b8eb26fe3ea9f8e9c2234e805a27b269e585842 \ - --hash=sha256:ec8d7d8567e14af34a7911c98f5ac74a3d4a743cd848643341fc92b12b3784ff \ - --hash=sha256:ed78c8e94f57b44292c1a0350f580e18d3a3c5c0800e253f1583580c1b417ad2 \ - --hash=sha256:eea8d9e20632d68f653455265b18c35f90965e26f30d4d92f831899d6682149b \ - --hash=sha256:ef8937dae823b889c0273dfa0f0f6c46a3658ac0d851349c464d1b00e7ff4252 \ - --hash=sha256:f06e3c4c0a8badfc4910b9fd15beb1ad8f3b8fafa8ea82c023e5e607b66a78e4 \ - --hash=sha256:f0821b9bdf18c5b7d51722b906b233a39b17f602501a966cfbd9b285f8ab83cd \ - --hash=sha256:f0ba13557fec9d5ffc0a22826754a7457cc77f1b25145be10b7bb1d143ce84c6 \ - --hash=sha256:f382fec4a7891d66fb7163c90754454030bb9200a13f82ee7860b6359f3f2fa8 \ - --hash=sha256:fe7aaf5a54821d340d21412f7f6e6272a9b17a0cbafc1d68f77f2fc11009dcd5 \ - --hash=sha256:ff38378346b7018f42cbc1f6d1d3778e36e16d8595f79a312b31e7c25c50bd08 \ - --hash=sha256:ffa1bb0e26297b0f22881b219ffc82a33a3c84ce6174a9d69406239b14575bd5 +rapidfuzz==3.11.0 # via -r src/backend/requirements.in -redis==5.2.1 \ - --hash=sha256:16f2e22dff21d5125e8481515e386711a34cbec50f0e44413dd7d9c060a54e0f \ - --hash=sha256:ee7e1056b9aea0f04c6c2ed59452947f34c4940ee025f5dd83e6a6418b6989e4 +redis==5.2.1 # via django-redis -referencing==0.36.0 \ - --hash=sha256:01fc2916bab821aa3284d645bbbb41ba39609e7ff47072416a39ec2fb04d10d9 \ - --hash=sha256:246db964bb6101905167895cd66499cfb2aabc5f83277d008c52afe918ef29ba +referencing==0.36.0 # via # jsonschema # jsonschema-specifications -requests==2.32.3 \ - --hash=sha256:55365417734eb18255590a9ff9eb97e9e1da868d4ccd6402399eaf68af20a760 \ - --hash=sha256:70761cfe03c773ceb22aa2f671b4757976145175cdfca038c02654d061d6dcc6 +requests==2.32.3 # via # coreapi # django-allauth # opentelemetry-exporter-otlp-proto-http # requests-oauthlib -requests-oauthlib==2.0.0 \ - --hash=sha256:7dd8a5c40426b779b0868c404bdef9768deccf22749cde15852df527e6269b36 \ - --hash=sha256:b3dffaebd884d8cd778494369603a9e7b58d29111bf6b41bdc2dcd87203af4e9 +requests-oauthlib==2.0.0 # via django-allauth -rpds-py==0.22.3 \ - --hash=sha256:009de23c9c9ee54bf11303a966edf4d9087cd43a6003672e6aa7def643d06518 \ - --hash=sha256:02fbb9c288ae08bcb34fb41d516d5eeb0455ac35b5512d03181d755d80810059 \ - --hash=sha256:0a0461200769ab3b9ab7e513f6013b7a97fdeee41c29b9db343f3c5a8e2b9e61 \ - --hash=sha256:0b09865a9abc0ddff4e50b5ef65467cd94176bf1e0004184eb915cbc10fc05c5 \ - --hash=sha256:0b8db6b5b2d4491ad5b6bdc2bc7c017eec108acbf4e6785f42a9eb0ba234f4c9 \ - --hash=sha256:0c150c7a61ed4a4f4955a96626574e9baf1adf772c2fb61ef6a5027e52803543 \ - --hash=sha256:0f3cec041684de9a4684b1572fe28c7267410e02450f4561700ca5a3bc6695a2 \ - --hash=sha256:1352ae4f7c717ae8cba93421a63373e582d19d55d2ee2cbb184344c82d2ae55a \ - --hash=sha256:177c7c0fce2855833819c98e43c262007f42ce86651ffbb84f37883308cb0e7d \ - --hash=sha256:1978d0021e943aae58b9b0b196fb4895a25cc53d3956b8e35e0b7682eefb6d56 \ - --hash=sha256:1a60bce91f81ddaac922a40bbb571a12c1070cb20ebd6d49c48e0b101d87300d \ - --hash=sha256:1aef18820ef3e4587ebe8b3bc9ba6e55892a6d7b93bac6d29d9f631a3b4befbd \ - --hash=sha256:1e9663daaf7a63ceccbbb8e3808fe90415b0757e2abddbfc2e06c857bf8c5e2b \ - --hash=sha256:20070c65396f7373f5df4005862fa162db5d25d56150bddd0b3e8214e8ef45b4 \ - --hash=sha256:214b7a953d73b5e87f0ebece4a32a5bd83c60a3ecc9d4ec8f1dca968a2d91e99 \ - --hash=sha256:22bebe05a9ffc70ebfa127efbc429bc26ec9e9b4ee4d15a740033efda515cf3d \ - --hash=sha256:24e8abb5878e250f2eb0d7859a8e561846f98910326d06c0d51381fed59357bd \ - --hash=sha256:26fd7cac7dd51011a245f29a2cc6489c4608b5a8ce8d75661bb4a1066c52dfbe \ - --hash=sha256:27b1d3b3915a99208fee9ab092b8184c420f2905b7d7feb4aeb5e4a9c509b8a1 \ - --hash=sha256:27e98004595899949bd7a7b34e91fa7c44d7a97c40fcaf1d874168bb652ec67e \ - --hash=sha256:2b8f60e1b739a74bab7e01fcbe3dddd4657ec685caa04681df9d562ef15b625f \ - --hash=sha256:2de29005e11637e7a2361fa151f780ff8eb2543a0da1413bb951e9f14b699ef3 \ - --hash=sha256:2e8b55d8517a2fda8d95cb45d62a5a8bbf9dd0ad39c5b25c8833efea07b880ca \ - --hash=sha256:2fa4331c200c2521512595253f5bb70858b90f750d39b8cbfd67465f8d1b596d \ - --hash=sha256:3445e07bf2e8ecfeef6ef67ac83de670358abf2996916039b16a218e3d95e97e \ - --hash=sha256:3453e8d41fe5f17d1f8e9c383a7473cd46a63661628ec58e07777c2fff7196dc \ - --hash=sha256:378753b4a4de2a7b34063d6f95ae81bfa7b15f2c1a04a9518e8644e81807ebea \ - --hash=sha256:3af6e48651c4e0d2d166dc1b033b7042ea3f871504b6805ba5f4fe31581d8d38 \ - --hash=sha256:3dfcbc95bd7992b16f3f7ba05af8a64ca694331bd24f9157b49dadeeb287493b \ - --hash=sha256:3f21f0495edea7fdbaaa87e633a8689cd285f8f4af5c869f27bc8074638ad69c \ - --hash=sha256:4041711832360a9b75cfb11b25a6a97c8fb49c07b8bd43d0d02b45d0b499a4ff \ - --hash=sha256:44d61b4b7d0c2c9ac019c314e52d7cbda0ae31078aabd0f22e583af3e0d79723 \ - --hash=sha256:4617e1915a539a0d9a9567795023de41a87106522ff83fbfaf1f6baf8e85437e \ - --hash=sha256:4b232061ca880db21fa14defe219840ad9b74b6158adb52ddf0e87bead9e8493 \ - --hash=sha256:5246b14ca64a8675e0a7161f7af68fe3e910e6b90542b4bfb5439ba752191df6 \ - --hash=sha256:5725dd9cc02068996d4438d397e255dcb1df776b7ceea3b9cb972bdb11260a83 \ - --hash=sha256:583f6a1993ca3369e0f80ba99d796d8e6b1a3a2a442dd4e1a79e652116413091 \ - --hash=sha256:59259dc58e57b10e7e18ce02c311804c10c5a793e6568f8af4dead03264584d1 \ - --hash=sha256:593eba61ba0c3baae5bc9be2f5232430453fb4432048de28399ca7376de9c627 \ - --hash=sha256:59f4a79c19232a5774aee369a0c296712ad0e77f24e62cad53160312b1c1eaa1 \ - --hash=sha256:5f0e260eaf54380380ac3808aa4ebe2d8ca28b9087cf411649f96bad6900c728 \ - --hash=sha256:62d9cfcf4948683a18a9aff0ab7e1474d407b7bab2ca03116109f8464698ab16 \ - --hash=sha256:64607d4cbf1b7e3c3c8a14948b99345eda0e161b852e122c6bb71aab6d1d798c \ - --hash=sha256:655ca44a831ecb238d124e0402d98f6212ac527a0ba6c55ca26f616604e60a45 \ - --hash=sha256:666ecce376999bf619756a24ce15bb14c5bfaf04bf00abc7e663ce17c3f34fe7 \ - --hash=sha256:68049202f67380ff9aa52f12e92b1c30115f32e6895cd7198fa2a7961621fc5a \ - --hash=sha256:69803198097467ee7282750acb507fba35ca22cc3b85f16cf45fb01cb9097730 \ - --hash=sha256:6c7b99ca52c2c1752b544e310101b98a659b720b21db00e65edca34483259967 \ - --hash=sha256:6dd9412824c4ce1aca56c47b0991e65bebb7ac3f4edccfd3f156150c96a7bf25 \ - --hash=sha256:70eb60b3ae9245ddea20f8a4190bd79c705a22f8028aaf8bbdebe4716c3fab24 \ - --hash=sha256:70fb28128acbfd264eda9bf47015537ba3fe86e40d046eb2963d75024be4d055 \ - --hash=sha256:7b2513ba235829860b13faa931f3b6846548021846ac808455301c23a101689d \ - --hash=sha256:7ef9d9da710be50ff6809fed8f1963fecdfecc8b86656cadfca3bc24289414b0 \ - --hash=sha256:81e69b0a0e2537f26d73b4e43ad7bc8c8efb39621639b4434b76a3de50c6966e \ - --hash=sha256:8633e471c6207a039eff6aa116e35f69f3156b3989ea3e2d755f7bc41754a4a7 \ - --hash=sha256:8bd7c8cfc0b8247c8799080fbff54e0b9619e17cdfeb0478ba7295d43f635d7c \ - --hash=sha256:9253fc214112405f0afa7db88739294295f0e08466987f1d70e29930262b4c8f \ - --hash=sha256:99b37292234e61325e7a5bb9689e55e48c3f5f603af88b1642666277a81f1fbd \ - --hash=sha256:9bd7228827ec7bb817089e2eb301d907c0d9827a9e558f22f762bb690b131652 \ - --hash=sha256:9beeb01d8c190d7581a4d59522cd3d4b6887040dcfc744af99aa59fef3e041a8 \ - --hash=sha256:a63cbdd98acef6570c62b92a1e43266f9e8b21e699c363c0fef13bd530799c11 \ - --hash=sha256:a76e42402542b1fae59798fab64432b2d015ab9d0c8c47ba7addddbaf7952333 \ - --hash=sha256:ac0a03221cdb5058ce0167ecc92a8c89e8d0decdc9e99a2ec23380793c4dcb96 \ - --hash=sha256:b0b4136a252cadfa1adb705bb81524eee47d9f6aab4f2ee4fa1e9d3cd4581f64 \ - --hash=sha256:b25bc607423935079e05619d7de556c91fb6adeae9d5f80868dde3468657994b \ - --hash=sha256:b3d504047aba448d70cf6fa22e06cb09f7cbd761939fdd47604f5e007675c24e \ - --hash=sha256:bb47271f60660803ad11f4c61b42242b8c1312a31c98c578f79ef9387bbde21c \ - --hash=sha256:bbb232860e3d03d544bc03ac57855cd82ddf19c7a07651a7c0fdb95e9efea8b9 \ - --hash=sha256:bc27863442d388870c1809a87507727b799c8460573cfbb6dc0eeaef5a11b5ec \ - --hash=sha256:bc51abd01f08117283c5ebf64844a35144a0843ff7b2983e0648e4d3d9f10dbb \ - --hash=sha256:be2eb3f2495ba669d2a985f9b426c1797b7d48d6963899276d22f23e33d47e37 \ - --hash=sha256:bf9db5488121b596dbfc6718c76092fda77b703c1f7533a226a5a9f65248f8ad \ - --hash=sha256:c58e2339def52ef6b71b8f36d13c3688ea23fa093353f3a4fee2556e62086ec9 \ - --hash=sha256:cfbc454a2880389dbb9b5b398e50d439e2e58669160f27b60e5eca11f68ae17c \ - --hash=sha256:cff63a0272fcd259dcc3be1657b07c929c466b067ceb1c20060e8d10af56f5bf \ - --hash=sha256:d115bffdd417c6d806ea9069237a4ae02f513b778e3789a359bc5856e0404cc4 \ - --hash=sha256:d20cfb4e099748ea39e6f7b16c91ab057989712d31761d3300d43134e26e165f \ - --hash=sha256:d48424e39c2611ee1b84ad0f44fb3b2b53d473e65de061e3f460fc0be5f1939d \ - --hash=sha256:e0fa2d4ec53dc51cf7d3bb22e0aa0143966119f42a0c3e4998293a3dd2856b09 \ - --hash=sha256:e32fee8ab45d3c2db6da19a5323bc3362237c8b653c70194414b892fd06a080d \ - --hash=sha256:e35ba67d65d49080e8e5a1dd40101fccdd9798adb9b050ff670b7d74fa41c566 \ - --hash=sha256:e3fb866d9932a3d7d0c82da76d816996d1667c44891bd861a0f97ba27e84fc74 \ - --hash=sha256:e61b02c3f7a1e0b75e20c3978f7135fd13cb6cf551bf4a6d29b999a88830a338 \ - --hash=sha256:e67ba3c290821343c192f7eae1d8fd5999ca2dc99994114643e2f2d3e6138b15 \ - --hash=sha256:e79dd39f1e8c3504be0607e5fc6e86bb60fe3584bec8b782578c3b0fde8d932c \ - --hash=sha256:e89391e6d60251560f0a8f4bd32137b077a80d9b7dbe6d5cab1cd80d2746f648 \ - --hash=sha256:ea7433ce7e4bfc3a85654aeb6747babe3f66eaf9a1d0c1e7a4435bbdf27fea84 \ - --hash=sha256:eaf16ae9ae519a0e237a0f528fd9f0197b9bb70f40263ee57ae53c2b8d48aeb3 \ - --hash=sha256:eb0c341fa71df5a4595f9501df4ac5abfb5a09580081dffbd1ddd4654e6e9123 \ - --hash=sha256:f276b245347e6e36526cbd4a266a417796fc531ddf391e43574cf6466c492520 \ - --hash=sha256:f47ad3d5f3258bd7058d2d506852217865afefe6153a36eb4b6928758041d831 \ - --hash=sha256:f56a6b404f74ab372da986d240e2e002769a7d7102cc73eb238a4f72eec5284e \ - --hash=sha256:f5cf2a0c2bdadf3791b5c205d55a37a54025c6e18a71c71f82bb536cf9a454bf \ - --hash=sha256:f5d36399a1b96e1a5fdc91e0522544580dbebeb1f77f27b2b0ab25559e103b8b \ - --hash=sha256:f60bd8423be1d9d833f230fdbccf8f57af322d96bcad6599e5a771b151398eb2 \ - --hash=sha256:f612463ac081803f243ff13cccc648578e2279295048f2a8d5eb430af2bae6e3 \ - --hash=sha256:f73d3fef726b3243a811121de45193c0ca75f6407fe66f3f4e183c983573e130 \ - --hash=sha256:f82a116a1d03628a8ace4859556fb39fd1424c933341a08ea3ed6de1edb0283b \ - --hash=sha256:fb0ba113b4983beac1a2eb16faffd76cb41e176bf58c4afe3e14b9c681f702de \ - --hash=sha256:fb4f868f712b2dd4bcc538b0a0c1f63a2b1d584c925e69a224d759e7070a12d5 \ - --hash=sha256:fb6116dfb8d1925cbdb52595560584db42a7f664617a1f7d7f6e32f138cdf37d \ - --hash=sha256:fda7cb070f442bf80b642cd56483b5548e43d366fe3f39b98e67cce780cded00 \ - --hash=sha256:feea821ee2a9273771bae61194004ee2fc33f8ec7db08117ef9147d4bbcbca8e +rpds-py==0.22.3 # via # jsonschema # referencing -sentry-sdk==2.20.0 \ - --hash=sha256:afa82713a92facf847df3c6f63cec71eb488d826a50965def3d7722aa6f0fdab \ - --hash=sha256:c359a1edf950eb5e80cffd7d9111f3dbeef57994cb4415df37d39fda2cf22364 +sentry-sdk==2.20.0 # via # -r src/backend/requirements.in # django-q-sentry -setuptools==75.8.0 \ - --hash=sha256:c5afc8f407c626b8313a86e10311dd3f661c6cd9c09d4bf8c15c0e11f9f2b0e6 \ - --hash=sha256:e3982f444617239225d675215d51f6ba05f845d4eec313da4418fdbb56fb27e3 +setuptools==75.8.0 # via # -r src/backend/requirements.in # django-money -sgmllib3k==1.0.0 \ - --hash=sha256:7868fb1c8bfa764c1ac563d3cf369c381d1325d36124933a726f29fcdaa812e9 +sgmllib3k==1.0.0 # via feedparser -six==1.17.0 \ - --hash=sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274 \ - --hash=sha256:ff70335d468e7eb6ec65b95b99d3a2836546063f63acc5171de367e834932a81 +six==1.17.0 # via # html5lib # python-dateutil -sqlparse==0.5.3 \ - --hash=sha256:09f67787f56a0b16ecdbde1bfc7f5d9c3371ca683cfeaa8e6ff60b4807ec9272 \ - --hash=sha256:cf2196ed3418f3ba5de6af7e82c694a9fbdbfecccdfc72e281548517081f16ca +sqlparse==0.5.3 # via # django # django-sql-utils -structlog==25.1.0 \ - --hash=sha256:2ef2a572e0e27f09664965d31a576afe64e46ac6084ef5cec3c2b8cd6e4e3ad3 \ - --hash=sha256:843fe4f254540329f380812cbe612e1af5ec5b8172205ae634679cd35a6d6321 +structlog==25.1.0 # via django-structlog -tablib[xls, xlsx, yaml]==3.7.0 \ - --hash=sha256:9a6930037cfe0f782377963ca3f2b1dae3fd4cdbf0883848f22f1447e7bb718b \ - --hash=sha256:f9db84ed398df5109bd69c11d46613d16cc572fb9ad3213f10d95e2b5f12c18e +tablib[xls, xlsx, yaml]==3.7.0 # via -r src/backend/requirements.in -tinycss2==1.4.0 \ - --hash=sha256:10c0972f6fc0fbee87c3edb76549357415e94548c1ae10ebccdea16fb404a9b7 \ - --hash=sha256:3a49cf47b7675da0b15d0c6e1df8df4ebd96e9394bb905a5775adb0d884c5289 +tinycss2==1.4.0 # via # bleach # cssselect2 # weasyprint -tomli==2.2.1 \ - --hash=sha256:023aa114dd824ade0100497eb2318602af309e5a55595f76b626d6d9f3b7b0a6 \ - --hash=sha256:02abe224de6ae62c19f090f68da4e27b10af2b93213d36cf44e6e1c5abd19fdd \ - --hash=sha256:286f0ca2ffeeb5b9bd4fcc8d6c330534323ec51b2f52da063b11c502da16f30c \ - --hash=sha256:2d0f2fdd22b02c6d81637a3c95f8cd77f995846af7414c5c4b8d0545afa1bc4b \ - --hash=sha256:33580bccab0338d00994d7f16f4c4ec25b776af3ffaac1ed74e0b3fc95e885a8 \ - --hash=sha256:400e720fe168c0f8521520190686ef8ef033fb19fc493da09779e592861b78c6 \ - --hash=sha256:40741994320b232529c802f8bc86da4e1aa9f413db394617b9a256ae0f9a7f77 \ - --hash=sha256:465af0e0875402f1d226519c9904f37254b3045fc5084697cefb9bdde1ff99ff \ - --hash=sha256:4a8f6e44de52d5e6c657c9fe83b562f5f4256d8ebbfe4ff922c495620a7f6cea \ - --hash=sha256:4e340144ad7ae1533cb897d406382b4b6fede8890a03738ff1683af800d54192 \ - --hash=sha256:678e4fa69e4575eb77d103de3df8a895e1591b48e740211bd1067378c69e8249 \ - --hash=sha256:6972ca9c9cc9f0acaa56a8ca1ff51e7af152a9f87fb64623e31d5c83700080ee \ - --hash=sha256:7fc04e92e1d624a4a63c76474610238576942d6b8950a2d7f908a340494e67e4 \ - --hash=sha256:889f80ef92701b9dbb224e49ec87c645ce5df3fa2cc548664eb8a25e03127a98 \ - --hash=sha256:8d57ca8095a641b8237d5b079147646153d22552f1c637fd3ba7f4b0b29167a8 \ - --hash=sha256:8dd28b3e155b80f4d54beb40a441d366adcfe740969820caf156c019fb5c7ec4 \ - --hash=sha256:9316dc65bed1684c9a98ee68759ceaed29d229e985297003e494aa825ebb0281 \ - --hash=sha256:a198f10c4d1b1375d7687bc25294306e551bf1abfa4eace6650070a5c1ae2744 \ - --hash=sha256:a38aa0308e754b0e3c67e344754dff64999ff9b513e691d0e786265c93583c69 \ - --hash=sha256:a92ef1a44547e894e2a17d24e7557a5e85a9e1d0048b0b5e7541f76c5032cb13 \ - --hash=sha256:ac065718db92ca818f8d6141b5f66369833d4a80a9d74435a268c52bdfa73140 \ - --hash=sha256:b82ebccc8c8a36f2094e969560a1b836758481f3dc360ce9a3277c65f374285e \ - --hash=sha256:c954d2250168d28797dd4e3ac5cf812a406cd5a92674ee4c8f123c889786aa8e \ - --hash=sha256:cb55c73c5f4408779d0cf3eef9f762b9c9f147a77de7b258bef0a5628adc85cc \ - --hash=sha256:cd45e1dc79c835ce60f7404ec8119f2eb06d38b1deba146f07ced3bbc44505ff \ - --hash=sha256:d3f5614314d758649ab2ab3a62d4f2004c825922f9e370b29416484086b264ec \ - --hash=sha256:d920f33822747519673ee656a4b6ac33e382eca9d331c87770faa3eef562aeb2 \ - --hash=sha256:db2b95f9de79181805df90bedc5a5ab4c165e6ec3fe99f970d0e302f384ad222 \ - --hash=sha256:e59e304978767a54663af13c07b3d1af22ddee3bb2fb0618ca1593e4f593a106 \ - --hash=sha256:e85e99945e688e32d5a35c1ff38ed0b3f41f43fad8df0bdf79f72b2ba7bc5272 \ - --hash=sha256:ece47d672db52ac607a3d9599a9d48dcb2f2f735c6c2d1f34130085bb12b112a \ - --hash=sha256:f4039b9cbc3048b2416cc57ab3bda989a6fcf9b36cf8937f01a6e731b64f80d7 +tomli==2.2.1 # via pip-licenses -typing-extensions==4.12.2 \ - --hash=sha256:04e5ca0351e0f3f85c6853954072df659d0d13fac324d0072316b67d7794700d \ - --hash=sha256:1a7ead55c7e559dd4dee8856e3a88b41225abfe1ce8df57b7c13915fe121ffb8 +typing-extensions==4.12.2 # via # asgiref # drf-spectacular @@ -1623,271 +391,43 @@ typing-extensions==4.12.2 \ # py-moneyed # referencing # structlog -tzdata==2024.2 \ - --hash=sha256:7d85cc416e9382e69095b7bdf4afd9e3880418a2413feec7069d533d6b4e31cc \ - --hash=sha256:a48093786cdcde33cad18c2555e8532f34422074448fbc874186f0abd79565cd +tzdata==2024.2 # via icalendar -uritemplate==4.1.1 \ - --hash=sha256:4346edfc5c3b79f694bccd6d6099a322bbeb628dbf2cd86eea55a456ce5124f0 \ - --hash=sha256:830c08b8d99bdd312ea4ead05994a38e8936266f84b9a7878232db50b044e02e +uritemplate==4.1.1 # via # coreapi # drf-spectacular -urllib3==2.3.0 \ - --hash=sha256:1cee9ad369867bfdbbb48b7dd50374c0967a0bb7710050facf0dd6911440e3df \ - --hash=sha256:f8c5449b3cf0861679ce7e0503c7b44b5ec981bec0d1d3795a07f1ba96f0204d +urllib3==2.3.0 # via # dulwich # requests # sentry-sdk -wcwidth==0.2.13 \ - --hash=sha256:3da69048e4540d84af32131829ff948f1e022c1c6bdb8d6102117aac784f6859 \ - --hash=sha256:72ea0c06399eb286d978fdedb6923a9eb47e1c486ce63e9b4e64fc18303972b5 +wcwidth==0.2.13 # via prettytable -weasyprint==62.3 \ - --hash=sha256:8d8680d732f7fa0fcbc587692a5a5cb095c3525627066918d6e203cbf42b7fcd \ - --hash=sha256:d31048646ce15084e135b33e334a61f526aa68d2f679fcc109ed0e0f5edaed21 +weasyprint==62.3 # via # -r src/backend/requirements.in # django-weasyprint -webencodings==0.5.1 \ - --hash=sha256:a0af1213f3c2226497a97e2b3aa01a7e4bee4f403f95be16fc9acd2947514a78 \ - --hash=sha256:b36a1c245f2d304965eb4e0a82848379241dc04b865afcc4aab16748587e1923 +webencodings==0.5.1 # via # bleach # cssselect2 # html5lib # tinycss2 -whitenoise==6.8.2 \ - --hash=sha256:486bd7267a375fa9650b136daaec156ac572971acc8bf99add90817a530dd1d4 \ - --hash=sha256:df12dce147a043d1956d81d288c6f0044147c6d2ab9726e5772ac50fb45d2280 +whitenoise==6.8.2 # via -r src/backend/requirements.in -wrapt==1.17.2 \ - --hash=sha256:08e7ce672e35efa54c5024936e559469436f8b8096253404faeb54d2a878416f \ - --hash=sha256:0a6e821770cf99cc586d33833b2ff32faebdbe886bd6322395606cf55153246c \ - --hash=sha256:0b929ac182f5ace000d459c59c2c9c33047e20e935f8e39371fa6e3b85d56f4a \ - --hash=sha256:129a150f5c445165ff941fc02ee27df65940fcb8a22a61828b1853c98763a64b \ - --hash=sha256:13e6afb7fe71fe7485a4550a8844cc9ffbe263c0f1a1eea569bc7091d4898555 \ - --hash=sha256:1473400e5b2733e58b396a04eb7f35f541e1fb976d0c0724d0223dd607e0f74c \ - --hash=sha256:18983c537e04d11cf027fbb60a1e8dfd5190e2b60cc27bc0808e653e7b218d1b \ - --hash=sha256:1a7ed2d9d039bd41e889f6fb9364554052ca21ce823580f6a07c4ec245c1f5d6 \ - --hash=sha256:1e1fe0e6ab7775fd842bc39e86f6dcfc4507ab0ffe206093e76d61cde37225c8 \ - --hash=sha256:1fb5699e4464afe5c7e65fa51d4f99e0b2eadcc176e4aa33600a3df7801d6662 \ - --hash=sha256:2696993ee1eebd20b8e4ee4356483c4cb696066ddc24bd70bcbb80fa56ff9061 \ - --hash=sha256:35621ae4c00e056adb0009f8e86e28eb4a41a4bfa8f9bfa9fca7d343fe94f998 \ - --hash=sha256:36ccae62f64235cf8ddb682073a60519426fdd4725524ae38874adf72b5f2aeb \ - --hash=sha256:3cedbfa9c940fdad3e6e941db7138e26ce8aad38ab5fe9dcfadfed9db7a54e62 \ - --hash=sha256:3d57c572081fed831ad2d26fd430d565b76aa277ed1d30ff4d40670b1c0dd984 \ - --hash=sha256:3fc7cb4c1c744f8c05cd5f9438a3caa6ab94ce8344e952d7c45a8ed59dd88392 \ - --hash=sha256:4011d137b9955791f9084749cba9a367c68d50ab8d11d64c50ba1688c9b457f2 \ - --hash=sha256:40d615e4fe22f4ad3528448c193b218e077656ca9ccb22ce2cb20db730f8d306 \ - --hash=sha256:410a92fefd2e0e10d26210e1dfb4a876ddaf8439ef60d6434f21ef8d87efc5b7 \ - --hash=sha256:41388e9d4d1522446fe79d3213196bd9e3b301a336965b9e27ca2788ebd122f3 \ - --hash=sha256:468090021f391fe0056ad3e807e3d9034e0fd01adcd3bdfba977b6fdf4213ea9 \ - --hash=sha256:49703ce2ddc220df165bd2962f8e03b84c89fee2d65e1c24a7defff6f988f4d6 \ - --hash=sha256:4a721d3c943dae44f8e243b380cb645a709ba5bd35d3ad27bc2ed947e9c68192 \ - --hash=sha256:4afd5814270fdf6380616b321fd31435a462019d834f83c8611a0ce7484c7317 \ - --hash=sha256:4c82b8785d98cdd9fed4cac84d765d234ed3251bd6afe34cb7ac523cb93e8b4f \ - --hash=sha256:4db983e7bca53819efdbd64590ee96c9213894272c776966ca6306b73e4affda \ - --hash=sha256:582530701bff1dec6779efa00c516496968edd851fba224fbd86e46cc6b73563 \ - --hash=sha256:58455b79ec2661c3600e65c0a716955adc2410f7383755d537584b0de41b1d8a \ - --hash=sha256:58705da316756681ad3c9c73fd15499aa4d8c69f9fd38dc8a35e06c12468582f \ - --hash=sha256:5bb1d0dbf99411f3d871deb6faa9aabb9d4e744d67dcaaa05399af89d847a91d \ - --hash=sha256:5c803c401ea1c1c18de70a06a6f79fcc9c5acfc79133e9869e730ad7f8ad8ef9 \ - --hash=sha256:5cbabee4f083b6b4cd282f5b817a867cf0b1028c54d445b7ec7cfe6505057cf8 \ - --hash=sha256:612dff5db80beef9e649c6d803a8d50c409082f1fedc9dbcdfde2983b2025b82 \ - --hash=sha256:62c2caa1585c82b3f7a7ab56afef7b3602021d6da34fbc1cf234ff139fed3cd9 \ - --hash=sha256:69606d7bb691b50a4240ce6b22ebb319c1cfb164e5f6569835058196e0f3a845 \ - --hash=sha256:6d9187b01bebc3875bac9b087948a2bccefe464a7d8f627cf6e48b1bbae30f82 \ - --hash=sha256:6ed6ffac43aecfe6d86ec5b74b06a5be33d5bb9243d055141e8cabb12aa08125 \ - --hash=sha256:703919b1633412ab54bcf920ab388735832fdcb9f9a00ae49387f0fe67dad504 \ - --hash=sha256:766d8bbefcb9e00c3ac3b000d9acc51f1b399513f44d77dfe0eb026ad7c9a19b \ - --hash=sha256:80dd7db6a7cb57ffbc279c4394246414ec99537ae81ffd702443335a61dbf3a7 \ - --hash=sha256:8112e52c5822fc4253f3901b676c55ddf288614dc7011634e2719718eaa187dc \ - --hash=sha256:8c8b293cd65ad716d13d8dd3624e42e5a19cc2a2f1acc74b30c2c13f15cb61a6 \ - --hash=sha256:8fdbdb757d5390f7c675e558fd3186d590973244fab0c5fe63d373ade3e99d40 \ - --hash=sha256:91bd7d1773e64019f9288b7a5101f3ae50d3d8e6b1de7edee9c2ccc1d32f0c0a \ - --hash=sha256:95c658736ec15602da0ed73f312d410117723914a5c91a14ee4cdd72f1d790b3 \ - --hash=sha256:99039fa9e6306880572915728d7f6c24a86ec57b0a83f6b2491e1d8ab0235b9a \ - --hash=sha256:9a2bce789a5ea90e51a02dfcc39e31b7f1e662bc3317979aa7e5538e3a034f72 \ - --hash=sha256:9a7d15bbd2bc99e92e39f49a04653062ee6085c0e18b3b7512a4f2fe91f2d681 \ - --hash=sha256:9abc77a4ce4c6f2a3168ff34b1da9b0f311a8f1cfd694ec96b0603dff1c79438 \ - --hash=sha256:9e8659775f1adf02eb1e6f109751268e493c73716ca5761f8acb695e52a756ae \ - --hash=sha256:9fee687dce376205d9a494e9c121e27183b2a3df18037f89d69bd7b35bcf59e2 \ - --hash=sha256:a5aaeff38654462bc4b09023918b7f21790efb807f54c000a39d41d69cf552cb \ - --hash=sha256:a604bf7a053f8362d27eb9fefd2097f82600b856d5abe996d623babd067b1ab5 \ - --hash=sha256:abbb9e76177c35d4e8568e58650aa6926040d6a9f6f03435b7a522bf1c487f9a \ - --hash=sha256:acc130bc0375999da18e3d19e5a86403667ac0c4042a094fefb7eec8ebac7cf3 \ - --hash=sha256:b18f2d1533a71f069c7f82d524a52599053d4c7166e9dd374ae2136b7f40f7c8 \ - --hash=sha256:b4e42a40a5e164cbfdb7b386c966a588b1047558a990981ace551ed7e12ca9c2 \ - --hash=sha256:b5e251054542ae57ac7f3fba5d10bfff615b6c2fb09abeb37d2f1463f841ae22 \ - --hash=sha256:b60fb58b90c6d63779cb0c0c54eeb38941bae3ecf7a73c764c52c88c2dcb9d72 \ - --hash=sha256:b870b5df5b71d8c3359d21be8f0d6c485fa0ebdb6477dda51a1ea54a9b558061 \ - --hash=sha256:ba0f0eb61ef00ea10e00eb53a9129501f52385c44853dbd6c4ad3f403603083f \ - --hash=sha256:bb87745b2e6dc56361bfde481d5a378dc314b252a98d7dd19a651a3fa58f24a9 \ - --hash=sha256:bb90fb8bda722a1b9d48ac1e6c38f923ea757b3baf8ebd0c82e09c5c1a0e7a04 \ - --hash=sha256:bc570b5f14a79734437cb7b0500376b6b791153314986074486e0b0fa8d71d98 \ - --hash=sha256:c86563182421896d73858e08e1db93afdd2b947a70064b813d515d66549e15f9 \ - --hash=sha256:c958bcfd59bacc2d0249dcfe575e71da54f9dcf4a8bdf89c4cb9a68a1170d73f \ - --hash=sha256:d18a4865f46b8579d44e4fe1e2bcbc6472ad83d98e22a26c963d46e4c125ef0b \ - --hash=sha256:d5e2439eecc762cd85e7bd37161d4714aa03a33c5ba884e26c81559817ca0925 \ - --hash=sha256:e3890b508a23299083e065f435a492b5435eba6e304a7114d2f919d400888cc6 \ - --hash=sha256:e496a8ce2c256da1eb98bd15803a79bee00fc351f5dfb9ea82594a3f058309e0 \ - --hash=sha256:e8b2816ebef96d83657b56306152a93909a83f23994f4b30ad4573b00bd11bb9 \ - --hash=sha256:eaf675418ed6b3b31c7a989fd007fa7c3be66ce14e5c3b27336383604c9da85c \ - --hash=sha256:ec89ed91f2fa8e3f52ae53cd3cf640d6feff92ba90d62236a81e4e563ac0e991 \ - --hash=sha256:ecc840861360ba9d176d413a5489b9a0aff6d6303d7e733e2c4623cfa26904a6 \ - --hash=sha256:f09b286faeff3c750a879d336fb6d8713206fc97af3adc14def0cdd349df6000 \ - --hash=sha256:f393cda562f79828f38a819f4788641ac7c4085f30f1ce1a68672baa686482bb \ - --hash=sha256:f917c1180fdb8623c2b75a99192f4025e412597c50b2ac870f156de8fb101119 \ - --hash=sha256:fc78a84e2dfbc27afe4b2bd7c80c8db9bca75cc5b85df52bfe634596a1da846b \ - --hash=sha256:ff04ef6eec3eee8a5efef2401495967a916feaa353643defcc03fc74fe213b58 +wrapt==1.17.2 # via # deprecated # opentelemetry-instrumentation # opentelemetry-instrumentation-redis -xlrd==2.0.1 \ - --hash=sha256:6a33ee89877bd9abc1158129f6e94be74e2679636b8a205b43b85206c3f0bbdd \ - --hash=sha256:f72f148f54442c6b056bf931dbc34f986fd0c3b0b6b5a58d013c9aef274d0c88 +xlrd==2.0.1 # via tablib -xlwt==1.3.0 \ - --hash=sha256:a082260524678ba48a297d922cc385f58278b8aa68741596a87de01a9c628b2e \ - --hash=sha256:c59912717a9b28f1a3c2a98fd60741014b06b043936dcecbc113eaaada156c88 +xlwt==1.3.0 # via tablib -xmlsec==1.3.14 \ - --hash=sha256:004e8a82e26728bf8a60f8ece1ef3ffafdac30ef538139dfe28870e8503ca64a \ - --hash=sha256:03ccba7dacf197850de954666af0221c740a5de631a80136362a1559223fab75 \ - --hash=sha256:0bae37b2920115cf00759ee9fb7841cbdebcef3a8a92734ab93ae8fa41ac581d \ - --hash=sha256:0be3b7a28e54a03b87faf07fb3c6dc3e50a2c79b686718c3ad08300b8bf6bb67 \ - --hash=sha256:1072878301cb9243a54679e0520e6a5be2266c07a28b0ecef9e029d05a90ffcd \ - --hash=sha256:12d90059308bb0c1b94bde065784e6852999d08b91bcb2048c17e62b954acb07 \ - --hash=sha256:147934bd39dfd840663fb6b920ea9201455fa886427975713f1b42d9f20b9b29 \ - --hash=sha256:19c86bab1498e4c2e56d8e2c878f461ccb6e56b67fd7522b0c8fda46d8910781 \ - --hash=sha256:1b9b5de6bc69fdec23147e5f712cb05dc86df105462f254f140d743cc680cc7b \ - --hash=sha256:1eb3dcf244a52f796377112d8f238dbb522eb87facffb498425dc8582a84a6bf \ - --hash=sha256:1fa1311f7489d050dde9028f5a2b5849c2927bb09c9a93491cb2f28fdc563912 \ - --hash=sha256:1fe23c2dd5f5dbcb24f40e2c1061e2672a32aabee7cf8ac5337036a485607d72 \ - --hash=sha256:204d3c586b8bd6f02a5d4c59850a8157205569d40c32567f49576fa5795d897d \ - --hash=sha256:2401e162aaab7d9416c3405bac7a270e5f370988a0f1f46f0f29b735edba87e1 \ - --hash=sha256:28cd9f513cf01dc0c5b9d9f0728714ecde2e7f46b3b6f63de91f4ae32f3008b3 \ - --hash=sha256:2f84a1c509c52773365645a87949081ee9ea9c535cd452048cc8ca4ad3b45666 \ - --hash=sha256:330147ce59fbe56a9be5b2085d739c55a569f112576b3f1b33681f87416eaf33 \ - --hash=sha256:34c61ec0c0e70fda710290ae74b9efe1928d9242ed82c4eecf97aa696cff68e6 \ - --hash=sha256:38e035bf48300b7dbde2dd01d3b8569f8584fc9c73809be13886e6b6c77b74fb \ - --hash=sha256:48e894ad3e7de373f56efc09d6a56f7eae73a8dd4cec8943313134849e9c6607 \ - --hash=sha256:4922afa9234d1c5763950b26c328a5320019e55eb6000272a79dfe54fee8e704 \ - --hash=sha256:4af81ce8044862ec865782efd353d22abdcd95b92364eef3c934de57ae6d5852 \ - --hash=sha256:4dea6df3ffcb65d0b215678c3a0fe7bbc66785d6eae81291296e372498bad43a \ - --hash=sha256:4edd8db4df04bbac9c4a5ab4af855b74fe2bf2c248d07cac2e6d92a485f1a685 \ - --hash=sha256:4fac2a787ae3b9fb761f9aec6b9f10f2d1c1b87abb574ebd8ff68435bdc97e3d \ - --hash=sha256:57fed3bc7943681c9ed4d2221600ab440f060d8d1a8f92f346f2b41effe175b8 \ - --hash=sha256:6566434e2e5c58e472362a6187f208601f1627a148683a6f92bd16479f1d9e20 \ - --hash=sha256:6679cec780386d848e7351d4b0de92c4483289ea4f0a2187e216159f939a4c6b \ - --hash=sha256:73eabf5ef58189d81655058cf328c1dfa9893d89f1bff5fc941481f08533f338 \ - --hash=sha256:774d5d1e45f07f953c1cc14fd055c1063f0725f7248b6b0e681f59fd8638934d \ - --hash=sha256:77749b338503fb6e151052c664064b34264f4168e2cb0cca1de78b7e5312a783 \ - --hash=sha256:7799a9ff3593f9dd43464e18b1a621640bffc40456c47c23383727f937dca7fc \ - --hash=sha256:7882963e9cb9c0bd0e8c2715a29159a366417ff4a30d8baf42b05bc5cf249446 \ - --hash=sha256:7e8e0171916026cbe8e2022c959558d02086655fd3c3466f2bc0451b09cf9ee8 \ - --hash=sha256:82ac81deb7d7bf5cc8a748148948e5df5386597ff43fb92ec651cc5c7addb0e7 \ - --hash=sha256:86ff7b2711557c1087b72b0a1a88d82eafbf2a6d38b97309a6f7101d4a7041c3 \ - --hash=sha256:934f804f2f895bcdb86f1eaee236b661013560ee69ec108d29cdd6e5f292a2d9 \ - --hash=sha256:995e87acecc263a2f6f2aa3cc204268f651cac8f4d7a2047f11b2cd49979cc38 \ - --hash=sha256:a487c3d144f791c32f5e560aa27a705fba23171728b8a8511f36de053ff6bc93 \ - --hash=sha256:a98eadfcb0c3b23ccceb7a2f245811f8d784bd287640dcfe696a26b9db1e2fc0 \ - --hash=sha256:ad1634cabe0915fe2a12e142db0ed2daf5be80cbe3891a2cecbba0750195cc6b \ - --hash=sha256:b109cdf717257fd4daa77c1d3ec8a3fb2a81318a6d06a36c55a8a53ae381ae5e \ - --hash=sha256:b6dd86f440fec9242515c64f0be93fec8b4289287db1f6de2651eee9995aaecb \ - --hash=sha256:b7ba2ea38e3d9efa520b14f3c0b7d99a7c055244ae5ba8bc9f4ca73b18f3a215 \ - --hash=sha256:ba3b39c493e3b04354615068a3218f30897fcc2f42c6d8986d0c1d63aca87782 \ - --hash=sha256:bd10ca3201f164482775a7ce61bf7ee9aade2e7d032046044dd0f6f52c91d79d \ - --hash=sha256:bddd2a2328b4e08c8a112e06cf2cd2b4d281f4ad94df15b4cef18f06cdc49d78 \ - --hash=sha256:c12900e1903e289deb84eb893dca88591d6884d3e3cda4fb711b8812118416e8 \ - --hash=sha256:c42735cc68fdb4c6065cf0a0701dfff3a12a1734c63a36376349af9a5481f27b \ - --hash=sha256:c4d41c83c8a2b8d8030204391ebeb6174fbdb044f0331653c4b5a4ce4150bcc0 \ - --hash=sha256:ce4e165a1436697e5e39587c4fba24db4545a5c9801e0d749f1afd09ad3ab901 \ - --hash=sha256:cf35a25be3eb6263b2e0544ba26294651113fab79064f994d347a2ca5973e8e2 \ - --hash=sha256:d0762f4232bce2c7f6c0af329db8b821b4460bbe123a2528fb5677d03db7a4b5 \ - --hash=sha256:dba457ff87c39cbae3c5020475a728d24bbd9d00376df9af9724cd3bb59ff07a \ - --hash=sha256:df4aa0782a53032fd35e18dcd6d328d6126324bfcfdef0cb5c2856f25b4b6f94 \ - --hash=sha256:e6cbc914d77678db0c8bc39e723d994174633d18f9d6be4665ec29cce978a96d \ - --hash=sha256:e732a75fcb6b84872b168f972fbbf3749baf76308635f14015d1d35ed0c5719c \ - --hash=sha256:ed4034939d8566ccdcd3b4e4f23c63fd807fb8763ae5668d59a19e11640a8242 +xmlsec==1.3.14 # via python3-saml -zipp==3.21.0 \ - --hash=sha256:2c9958f6430a2040341a52eb608ed6dd93ef4392e02ffe219417c1b28b5dd1f4 \ - --hash=sha256:ac1bbe05fd2991f160ebce24ffbac5f6d11d83dc90891255885223d42b3cd931 +zipp==3.21.0 # via importlib-metadata -zopfli==0.2.3.post1 \ - --hash=sha256:0aa5f90d6298bda02a95bc8dc8c3c19004d5a4e44bda00b67ca7431d857b4b54 \ - --hash=sha256:0cc20b02a9531559945324c38302fd4ba763311632d0ec8a1a0aa9c10ea363e6 \ - --hash=sha256:1d8cc06605519e82b16df090e17cb3990d1158861b2872c3117f1168777b81e4 \ - --hash=sha256:1f990634fd5c5c8ced8edddd8bd45fab565123b4194d6841e01811292650acae \ - --hash=sha256:2345e713260a350bea0b01a816a469ea356bc2d63d009a0d777691ecbbcf7493 \ - --hash=sha256:2768c877f76c8a0e7519b1c86c93757f3c01492ddde55751e9988afb7eff64e1 \ - --hash=sha256:29ea74e72ffa6e291b8c6f2504ce6c146b4fe990c724c1450eb8e4c27fd31431 \ - --hash=sha256:34a99592f3d9eb6f737616b5bd74b48a589fdb3cb59a01a50d636ea81d6af272 \ - --hash=sha256:3654bfc927bc478b1c3f3ff5056ed7b20a1a37fa108ca503256d0a699c03bbb1 \ - --hash=sha256:3657e416ffb8f31d9d3424af12122bb251befae109f2e271d87d825c92fc5b7b \ - --hash=sha256:37d011e92f7b9622742c905fdbed9920a1d0361df84142807ea2a528419dea7f \ - --hash=sha256:3827170de28faf144992d3d4dcf8f3998fe3c8a6a6f4a08f1d42c2ec6119d2bb \ - --hash=sha256:39e576f93576c5c223b41d9c780bbb91fd6db4babf3223d2a4fe7bf568e2b5a8 \ - --hash=sha256:3a89277ed5f8c0fb2d0b46d669aa0633123aa7381f1f6118c12f15e0fb48f8ca \ - --hash=sha256:3c163911f8bad94b3e1db0a572e7c28ba681a0c91d0002ea1e4fa9264c21ef17 \ - --hash=sha256:3f0197b6aa6eb3086ae9e66d6dd86c4d502b6c68b0ec490496348ae8c05ecaef \ - --hash=sha256:48dba9251060289101343110ab47c0756f66f809bb4d1ddbb6d5c7e7752115c5 \ - --hash=sha256:4915a41375bdee4db749ecd07d985a0486eb688a6619f713b7bf6fbfd145e960 \ - --hash=sha256:4c1226a7e2c7105ac31503a9bb97454743f55d88164d6d46bc138051b77f609b \ - --hash=sha256:4e50ffac74842c1c1018b9b73875a0d0a877c066ab06bf7cccbaa84af97e754f \ - --hash=sha256:518f1f4ed35dd69ce06b552f84e6d081f07c552b4c661c5312d950a0b764a58a \ - --hash=sha256:5aad740b4d4fcbaaae4887823925166ffd062db3b248b3f432198fc287381d1a \ - --hash=sha256:5f272186e03ad55e7af09ab78055535c201b1a0bcc2944edb1768298d9c483a4 \ - --hash=sha256:5fcfc0dc2761e4fcc15ad5d273b4d58c2e8e059d3214a7390d4d3c8e2aee644e \ - --hash=sha256:60db20f06c3d4c5934b16cfa62a2cc5c3f0686bffe0071ed7804d3c31ab1a04e \ - --hash=sha256:615a8ac9dda265e9cc38b2a76c3142e4a9f30fea4a79c85f670850783bc6feb4 \ - --hash=sha256:6482db9876c68faac2d20a96b566ffbf65ddaadd97b222e4e73641f4f8722fc4 \ - --hash=sha256:6617fb10f9e4393b331941861d73afb119cd847e88e4974bdbe8068ceef3f73f \ - --hash=sha256:676919fba7311125244eb0c4393679ac5fe856e5864a15d122bd815205369fa0 \ - --hash=sha256:6c2d2bc8129707e34c51f9352c4636ca313b52350bbb7e04637c46c1818a2a70 \ - --hash=sha256:71390dbd3fbf6ebea9a5d85ffed8c26ee1453ee09248e9b88486e30e0397b775 \ - --hash=sha256:716cdbfc57bfd3d3e31a58e6246e8190e6849b7dbb7c4ce39ef8bbf0edb8f6d5 \ - --hash=sha256:75a26a2307b10745a83b660c404416e984ee6fca515ec7f0765f69af3ce08072 \ - --hash=sha256:7be5cc6732eb7b4df17305d8a7b293223f934a31783a874a01164703bc1be6cd \ - --hash=sha256:7cce242b5df12b2b172489daf19c32e5577dd2fac659eb4b17f6a6efb446fd5c \ - --hash=sha256:81c341d9bb87a6dbbb0d45d6e272aca80c7c97b4b210f9b6e233bf8b87242f29 \ - --hash=sha256:89899641d4de97dbad8e0cde690040d078b6aea04066dacaab98e0b5a23573f2 \ - --hash=sha256:8d5ab297d660b75c159190ce6d73035502310e40fd35170aed7d1a1aea7ddd65 \ - --hash=sha256:8fbe5bcf10d01aab3513550f284c09fef32f342b36f56bfae2120a9c4d12c130 \ - --hash=sha256:91a2327a4d7e77471fa4fbb26991c6de4a738c6fc6a33e09bb25f56a870a4b7b \ - --hash=sha256:95a260cafd56b8fffa679918937401c80bb38e1681c448b988022e4c3610965d \ - --hash=sha256:96484dc0f48be1c5d7ae9f38ed1ce41e3675fd506b27c11a6607f14b49101e99 \ - --hash=sha256:9a6aec38a989bad7ddd1ef53f1265699e49e294d08231b5313d61293f3cd6237 \ - --hash=sha256:9ba214f4f45bec195ee8559651154d3ac2932470b9d91c5715fc29c013349f8c \ - --hash=sha256:9f4a7ec2770e6af05f5a02733fd3900f30a9cd58e5d6d3727e14c5bcd6e7d587 \ - --hash=sha256:a1cf720896d2ce998bc8e051d4b4ce0d8bec007aab6243102e8e1d22a0b2fb3f \ - --hash=sha256:a241a68581d34d67b40c425cce3d1fd211c092f99d9250947824ccba9f491949 \ - --hash=sha256:a53b18797cdef27e019db595d66c4b077325afe2fd62145953275f53d84ce40c \ - --hash=sha256:a82fc2dbebe6eb908b9c665e71496f8525c1bc4d2e3a7a7722ef2b128b6227c8 \ - --hash=sha256:a86eb88e06bd87e1fff31dac878965c26b0c26db59ddcf78bb0379a954b120de \ - --hash=sha256:aa588b21044f8a74e423d8c8a4c7fc9988501878aacced793467010039c50734 \ - --hash=sha256:b05296e8bc88c92e2b21e0a9bae4740c1551ee613c1d93a51fd28a7a0b2b6fbb \ - --hash=sha256:b0ec13f352ea5ae0fc91f98a48540512eed0767d0ec4f7f3cb92d92797983d18 \ - --hash=sha256:b3df42f52502438ee973042cc551877d24619fa1cd38ef7b7e9ac74200daca8b \ - --hash=sha256:b78008a69300d929ca2efeffec951b64a312e9a811e265ea4a907ab546d79fa6 \ - --hash=sha256:b9026a21b6d41eb0e2e63f5bc1242c3fcc43ecb770963cda99a4307863dac12e \ - --hash=sha256:bbe429fc50686bb2a2608a30843e36fbaa123462a5284f136c7d9e0145220bfd \ - --hash=sha256:bfa1eb759e07d8b7aa7a310a2bc535e127ee70addf90dc8d4b946b593c3e51a8 \ - --hash=sha256:c1e0ed5d84ffa2d677cc9582fc01e61dab2e7ef8b8996e055f0a76167b1b94df \ - --hash=sha256:c4278d1873ce6e803e5d4f8d702fd3026bd67fca744aa98881324d1157ddf748 \ - --hash=sha256:cac2b37ab21c2b36a10b685b1893ebd6b0f83ae26004838ac817680881576567 \ - --hash=sha256:cbe6df25807227519debd1a57ab236f5f6bad441500e85b13903e51f93a43214 \ - --hash=sha256:cd2c002f160502608dcc822ed2441a0f4509c52e86fcfd1a09e937278ed1ca14 \ - --hash=sha256:e0137dd64a493ba6a4be37405cfd6febe650a98cc1e9dca8f6b8c63b1db11b41 \ - --hash=sha256:e63d558847166543c2c9789e6f985400a520b7eacc4b99181668b2c3aeadd352 \ - --hash=sha256:eb45a34f23da4f8bc712b6376ca5396914b0b7c09adbb001dad964eb7f3132f8 \ - --hash=sha256:ecb7572df5372abce8073df078207d9d1749f20b8b136089916a4a0868d56051 \ - --hash=sha256:f12000a6accdd4bf0a3fa6eaa1b1c7a7bc80af0a2edf3f89d770d3dcce1d0e22 \ - --hash=sha256:f7d69c1a7168ad0e9cb864e8663acb232986a0c9c9cb9801f56bf6214f53a54d \ - --hash=sha256:f815fcc2b2a457977724bad97fb4854022980f51ce7b136925e336b530545ae1 \ - --hash=sha256:fc39f5c27f962ec8660d8d20c24762431131b5d8c672b44b0a54cf2b5bcde9b9 +zopfli==0.2.3.post1 # via fonttools diff --git a/tasks.py b/tasks.py index 709bc4ee719d..142e8b019d43 100644 --- a/tasks.py +++ b/tasks.py @@ -324,14 +324,14 @@ def install(c, uv=False, skip_plugins=False): ) run( c, - f'pip3 install --no-cache-dir --disable-pip-version-check -U --require-hashes -r {INSTALL_FILE}', + f'pip3 install --no-cache-dir --disable-pip-version-check -U -r {INSTALL_FILE}', ) else: run( c, 'pip3 install --no-cache-dir --disable-pip-version-check -U uv setuptools', ) - run(c, f'uv pip install -U --require-hashes -r {INSTALL_FILE}') + run(c, f'uv pip install -U -r {INSTALL_FILE}') # Run plugins install if not skip_plugins: From 9bfc0d265c58d49b01fbc3d3fdcf124f936b05c3 Mon Sep 17 00:00:00 2001 From: Matthias Mair Date: Sun, 26 Jan 2025 16:52:36 +0100 Subject: [PATCH 117/156] simplify customisation --- .../InvenTree/InvenTree/auth_overrides.py | 20 +++++-------------- 1 file changed, 5 insertions(+), 15 deletions(-) diff --git a/src/backend/InvenTree/InvenTree/auth_overrides.py b/src/backend/InvenTree/InvenTree/auth_overrides.py index 40394297093d..373018151707 100644 --- a/src/backend/InvenTree/InvenTree/auth_overrides.py +++ b/src/backend/InvenTree/InvenTree/auth_overrides.py @@ -179,12 +179,6 @@ def send_mail(self, template_prefix, email, context): return False - def get_email_confirmation_url(self, request, emailconfirmation): - """Construct the email confirmation url.""" - url = super().get_email_confirmation_url(request, emailconfirmation) - url = InvenTree.helpers_model.construct_absolute_url(url) - return url - def send_password_reset_mail(self, user, email, context): """Send the password reset mail.""" if not get_global_setting('LOGIN_ENABLE_PWD_FORGOT'): @@ -228,9 +222,9 @@ class CustomHeadlessAdapter(DefaultHeadlessAdapter): def get_frontend_url(self, request: HttpRequest, urlname, **kwargs): """Get the frontend URL for the given URL name respecting the request.""" HEADLESS_FRONTEND_URLS = { - 'account_confirm_email': ['verify-email/', '{key}'], + 'account_confirm_email': 'verify-email/{key}', 'account_reset_password': 'reset-password', - 'account_reset_password_from_key': ['set-password?key=', '{key}'], + 'account_reset_password_from_key': 'set-password?key={key}', 'account_signup': 'register', 'socialaccount_login_error': 'social-login-error', } @@ -239,13 +233,9 @@ def get_frontend_url(self, request: HttpRequest, urlname, **kwargs): f'URL name "{urlname}" not found in HEADLESS_FRONTEND_URLS' ) - url = HEADLESS_FRONTEND_URLS[urlname] - if isinstance(url, list): - return ( - request.build_absolute_uri(f'/{settings.FRONTEND_URL_BASE}/{url[0]}') - + url[1] - ) - return request.build_absolute_uri(f'/{settings.FRONTEND_URL_BASE}/{url}') + return request.build_absolute_uri( + f'/{settings.FRONTEND_URL_BASE}/{HEADLESS_FRONTEND_URLS[urlname].format(**kwargs)}' + ) class DRFTokenStrategy(SessionTokenStrategy): From 79f173f7cedab8710722b47cef21f8800ac1a946 Mon Sep 17 00:00:00 2001 From: Matthias Mair Date: Sun, 26 Jan 2025 17:04:59 +0100 Subject: [PATCH 118/156] implement email-verification --- src/frontend/src/enums/ApiEndpoints.tsx | 1 + src/frontend/src/pages/Auth/VerifyEmail.tsx | 61 +++++++++++++++++++++ src/frontend/src/router.tsx | 4 ++ 3 files changed, 66 insertions(+) create mode 100644 src/frontend/src/pages/Auth/VerifyEmail.tsx diff --git a/src/frontend/src/enums/ApiEndpoints.tsx b/src/frontend/src/enums/ApiEndpoints.tsx index 2f87246336c1..cca0ec51911a 100644 --- a/src/frontend/src/enums/ApiEndpoints.tsx +++ b/src/frontend/src/enums/ApiEndpoints.tsx @@ -29,6 +29,7 @@ export enum ApiEndpoints { auth_totp = 'auth/v1/account/authenticators/totp', auth_reauthenticate = 'auth/v1/auth/reauthenticate', auth_email = 'auth/v1/account/email', + auth_email_verify = 'auth/v1/auth/email/verify', auth_providers = 'auth/v1/account/providers', auth_provider_redirect = 'auth/v1/auth/provider/redirect', auth_config = 'auth/v1/config', diff --git a/src/frontend/src/pages/Auth/VerifyEmail.tsx b/src/frontend/src/pages/Auth/VerifyEmail.tsx new file mode 100644 index 000000000000..c6e098252df3 --- /dev/null +++ b/src/frontend/src/pages/Auth/VerifyEmail.tsx @@ -0,0 +1,61 @@ +import { Trans, t } from '@lingui/macro'; +import { Button, Center, Container, Stack, Title } from '@mantine/core'; +import { notifications } from '@mantine/notifications'; +import { useEffect } from 'react'; +import { useNavigate, useParams } from 'react-router-dom'; + +import { api } from '../../App'; +import { LanguageContext } from '../../contexts/LanguageContext'; +import { ApiEndpoints } from '../../enums/ApiEndpoints'; +import { apiUrl } from '../../states/ApiState'; + +export default function VerifyEmail() { + const { key } = useParams(); + const navigate = useNavigate(); + + function invalidKey() { + notifications.show({ + title: t`Key invalid`, + message: t`You need to provide a valid key.`, + color: 'red' + }); + navigate('/login'); + } + + useEffect(() => { + // make sure we have a key + if (!key) { + invalidKey(); + } + }, [key]); + + function handleSet() { + // Set password with call to backend + api + .post(apiUrl(ApiEndpoints.auth_email_verify), { + key: key + }) + .then((val) => { + if (val.status === 200) { + navigate('/login'); + } + }); + } + + return ( + +
+ + + + <Trans>Verify Email</Trans> + + + + +
+
+ ); +} diff --git a/src/frontend/src/router.tsx b/src/frontend/src/router.tsx index ad4a1b7fea2d..bc0f1ad4b69b 100644 --- a/src/frontend/src/router.tsx +++ b/src/frontend/src/router.tsx @@ -116,6 +116,9 @@ export const ChangePassword = Loadable( export const ResetPassword = Loadable( lazy(() => import('./pages/Auth/ResetPassword')) ); +export const VerifyEmail = Loadable( + lazy(() => import('./pages/Auth/VerifyEmail')) +); // Routes export const routes = ( @@ -178,6 +181,7 @@ export const routes = ( } /> } /> } /> + } /> ); From 87b083da6817fb356a89300f552d9f00016e123d Mon Sep 17 00:00:00 2001 From: Matthias Mair Date: Sun, 26 Jan 2025 17:13:28 +0100 Subject: [PATCH 119/156] remove auth from api docs --- docs/extract_schema.py | 1 - 1 file changed, 1 deletion(-) diff --git a/docs/extract_schema.py b/docs/extract_schema.py index b6b70a9db1ff..647a4a7b6ea2 100644 --- a/docs/extract_schema.py +++ b/docs/extract_schema.py @@ -14,7 +14,6 @@ # List of special paths we want to split out SPECIAL_PATHS = { - 'auth': 'Authorization and Authentication', 'background-task': 'Background Task Management', 'barcode': 'Barcode Scanning', 'bom': 'Bill of Materials', From a98faf328f272f603aa875a3dbd53762fe0a7406 Mon Sep 17 00:00:00 2001 From: Matthias Mair Date: Sun, 26 Jan 2025 18:12:15 +0100 Subject: [PATCH 120/156] fix override of get_frontend_url details in https://codeberg.org/allauth/django-allauth/pulls/4248 --- src/backend/InvenTree/InvenTree/auth_overrides.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/backend/InvenTree/InvenTree/auth_overrides.py b/src/backend/InvenTree/InvenTree/auth_overrides.py index 373018151707..80c05c65aedb 100644 --- a/src/backend/InvenTree/InvenTree/auth_overrides.py +++ b/src/backend/InvenTree/InvenTree/auth_overrides.py @@ -4,7 +4,7 @@ from django.conf import settings from django.contrib.auth.models import Group from django.core.exceptions import PermissionDenied -from django.http import HttpRequest, HttpResponseRedirect +from django.http import HttpResponseRedirect from django.urls import reverse from django.utils.translation import gettext_lazy as _ @@ -219,7 +219,7 @@ def get_connect_redirect_url(self, request, socialaccount): class CustomHeadlessAdapter(DefaultHeadlessAdapter): """Override of adapter to use dynamic settings.""" - def get_frontend_url(self, request: HttpRequest, urlname, **kwargs): + def get_frontend_url(self, urlname, **kwargs): """Get the frontend URL for the given URL name respecting the request.""" HEADLESS_FRONTEND_URLS = { 'account_confirm_email': 'verify-email/{key}', @@ -233,7 +233,7 @@ def get_frontend_url(self, request: HttpRequest, urlname, **kwargs): f'URL name "{urlname}" not found in HEADLESS_FRONTEND_URLS' ) - return request.build_absolute_uri( + return self.request.build_absolute_uri( f'/{settings.FRONTEND_URL_BASE}/{HEADLESS_FRONTEND_URLS[urlname].format(**kwargs)}' ) From dc07a8af3a77e4373cc40203dec15fbd357b5aa9 Mon Sep 17 00:00:00 2001 From: Matthias Mair Date: Wed, 29 Jan 2025 00:12:21 +0100 Subject: [PATCH 121/156] bump api again --- src/backend/InvenTree/InvenTree/api_version.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/backend/InvenTree/InvenTree/api_version.py b/src/backend/InvenTree/InvenTree/api_version.py index 314d60ceb67a..a57061023d0c 100644 --- a/src/backend/InvenTree/InvenTree/api_version.py +++ b/src/backend/InvenTree/InvenTree/api_version.py @@ -1,13 +1,13 @@ """InvenTree API version information.""" # InvenTree API version -INVENTREE_API_VERSION = 305 +INVENTREE_API_VERSION = 306 """Increment this API version number whenever there is a significant change to the API that any clients need to know about.""" INVENTREE_API_TEXT = """ -v305 - 2025-01-25 : https://github.com/inventree/InvenTree/pull/6293 +v306 - 2025-01-29 : https://github.com/inventree/InvenTree/pull/6293 - Removes a considerable amount of old auth endpoints - Introduces allauth based REST API From 3da0d029658c19f05b6f809239ac1e4544b8cdfd Mon Sep 17 00:00:00 2001 From: Matthias Mair Date: Mon, 3 Feb 2025 01:12:27 +0100 Subject: [PATCH 122/156] fix req --- src/backend/requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/backend/requirements.txt b/src/backend/requirements.txt index e09817c34a86..a5a44ae798ea 100644 --- a/src/backend/requirements.txt +++ b/src/backend/requirements.txt @@ -77,7 +77,7 @@ django==4.2.18 # djangorestframework # djangorestframework-simplejwt # drf-spectacular -django-allauth[mfa, openid, saml, socialaccount] @ git+https://codeberg.org/allauth/django-allauth@b802a0ff6206a8b7b6c9ecf7148d414f6868d47a +django-allauth[mfa, openid, saml, socialaccount] @ git+https://codeberg.org/allauth/django-allauth@b84141e2fa045bbfdb55e4161c6016f4094fb059 # via -r src/backend/requirements.in django-cleanup==9.0.0 # via -r src/backend/requirements.in From ebd20a9ccd13843716416dbfa00780b23eb4142b Mon Sep 17 00:00:00 2001 From: Matthias Mair Date: Thu, 6 Feb 2025 21:47:39 +0100 Subject: [PATCH 123/156] Revert "remove hash requirement for now" This reverts commit 00bb6c5274ee673948280ec084831edfa40ec3de. --- .pre-commit-config.yaml | 2 +- pyproject.toml | 1 + src/backend/requirements.txt | 1742 +++++++++++++++++++++++++++++++--- tasks.py | 4 +- 4 files changed, 1606 insertions(+), 143 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 999ae6e0da82..af4fab920c9b 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -36,7 +36,7 @@ repos: files: src/backend/requirements-dev\.(in|txt)$ - id: pip-compile name: pip-compile requirements.txt - args: [src/backend/requirements.in, -o, src/backend/requirements.txt, --no-strip-extras] + args: [src/backend/requirements.in, -o, src/backend/requirements.txt, --no-strip-extras, --generate-hashes] files: src/backend/requirements\.(in|txt)$ - id: pip-compile name: pip-compile requirements.txt diff --git a/pyproject.toml b/pyproject.toml index 32ab9e0d4392..d596e0f0b60b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -91,6 +91,7 @@ line-ending = "auto" [tool.uv.pip] python-version = "3.9" no-strip-extras=true +generate-hashes=true [tool.coverage.run] source = ["src/backend/InvenTree", "InvenTree"] diff --git a/src/backend/requirements.txt b/src/backend/requirements.txt index feafee5549ca..1f65c0ba2f53 100644 --- a/src/backend/requirements.txt +++ b/src/backend/requirements.txt @@ -1,54 +1,389 @@ # This file was autogenerated by uv via the following command: -# uv pip compile src/backend/requirements.in -o src/backend/requirements.txt --no-strip-extras -asgiref==3.8.1 +# uv pip compile src/backend/requirements.in -o src/backend/requirements.txt --no-strip-extras --generate-hashes +asgiref==3.8.1 \ + --hash=sha256:3e1e3ecc849832fe52ccf2cb6686b7a55f82bb1d6aee72a58826471390335e47 \ + --hash=sha256:c343bd80a0bec947a9860adb4c432ffa7db769836c64238fc34bdc3fec84d590 # via # django # django-allauth # django-cors-headers # django-structlog -async-timeout==5.0.1 +async-timeout==5.0.1 \ + --hash=sha256:39e3809566ff85354557ec2398b55e096c8364bacac9405a7a1fa429e77fe76c \ + --hash=sha256:d9321a7a3d5a6a5e187e824d2fa0793ce379a202935782d555d6e9d2735677d3 # via redis -attrs==25.1.0 +attrs==24.3.0 \ + --hash=sha256:8f5c07333d543103541ba7be0e2ce16eeee8130cb0b3f9238ab904ce1e85baff \ + --hash=sha256:ac96cd038792094f438ad1f6ff80837353805ac950cd2aa0e0625ef19850c308 # via # jsonschema # referencing -babel==2.17.0 +babel==2.16.0 \ + --hash=sha256:368b5b98b37c06b7daf6696391c3240c938b37767d4584413e8438c5c435fa8b \ + --hash=sha256:d1f3554ca26605fe173f3de0c65f750f5a42f924499bf134de6423582298e316 # via py-moneyed -bleach[css]==6.2.0 +bleach[css]==6.2.0 \ + --hash=sha256:117d9c6097a7c3d22fd578fcd8d35ff1e125df6736f554da4e432fdd63f31e5e \ + --hash=sha256:123e894118b8a599fd80d3ec1a6d4cc7ce4e5882b1317a7e1ba69b56e95f991f # via django-markdownify -brotli==1.1.0 +brotli==1.1.0 \ + --hash=sha256:03d20af184290887bdea3f0f78c4f737d126c74dc2f3ccadf07e54ceca3bf208 \ + --hash=sha256:0541e747cce78e24ea12d69176f6a7ddb690e62c425e01d31cc065e69ce55b48 \ + --hash=sha256:069a121ac97412d1fe506da790b3e69f52254b9df4eb665cd42460c837193354 \ + --hash=sha256:0737ddb3068957cf1b054899b0883830bb1fec522ec76b1098f9b6e0f02d9419 \ + --hash=sha256:0b63b949ff929fbc2d6d3ce0e924c9b93c9785d877a21a1b678877ffbbc4423a \ + --hash=sha256:0c6244521dda65ea562d5a69b9a26120769b7a9fb3db2fe9545935ed6735b128 \ + --hash=sha256:11d00ed0a83fa22d29bc6b64ef636c4552ebafcef57154b4ddd132f5638fbd1c \ + --hash=sha256:141bd4d93984070e097521ed07e2575b46f817d08f9fa42b16b9b5f27b5ac088 \ + --hash=sha256:19c116e796420b0cee3da1ccec3b764ed2952ccfcc298b55a10e5610ad7885f9 \ + --hash=sha256:1ab4fbee0b2d9098c74f3057b2bc055a8bd92ccf02f65944a241b4349229185a \ + --hash=sha256:1ae56aca0402a0f9a3431cddda62ad71666ca9d4dc3a10a142b9dce2e3c0cda3 \ + --hash=sha256:1b2c248cd517c222d89e74669a4adfa5577e06ab68771a529060cf5a156e9757 \ + --hash=sha256:1e9a65b5736232e7a7f91ff3d02277f11d339bf34099a56cdab6a8b3410a02b2 \ + --hash=sha256:224e57f6eac61cc449f498cc5f0e1725ba2071a3d4f48d5d9dffba42db196438 \ + --hash=sha256:22fc2a8549ffe699bfba2256ab2ed0421a7b8fadff114a3d201794e45a9ff578 \ + --hash=sha256:23032ae55523cc7bccb4f6a0bf368cd25ad9bcdcc1990b64a647e7bbcce9cb5b \ + --hash=sha256:2333e30a5e00fe0fe55903c8832e08ee9c3b1382aacf4db26664a16528d51b4b \ + --hash=sha256:2954c1c23f81c2eaf0b0717d9380bd348578a94161a65b3a2afc62c86467dd68 \ + --hash=sha256:2a24c50840d89ded6c9a8fdc7b6ed3692ed4e86f1c4a4a938e1e92def92933e0 \ + --hash=sha256:2de9d02f5bda03d27ede52e8cfe7b865b066fa49258cbab568720aa5be80a47d \ + --hash=sha256:2feb1d960f760a575dbc5ab3b1c00504b24caaf6986e2dc2b01c09c87866a943 \ + --hash=sha256:30924eb4c57903d5a7526b08ef4a584acc22ab1ffa085faceb521521d2de32dd \ + --hash=sha256:316cc9b17edf613ac76b1f1f305d2a748f1b976b033b049a6ecdfd5612c70409 \ + --hash=sha256:32d95b80260d79926f5fab3c41701dbb818fde1c9da590e77e571eefd14abe28 \ + --hash=sha256:38025d9f30cf4634f8309c6874ef871b841eb3c347e90b0851f63d1ded5212da \ + --hash=sha256:39da8adedf6942d76dc3e46653e52df937a3c4d6d18fdc94a7c29d263b1f5b50 \ + --hash=sha256:3c0ef38c7a7014ffac184db9e04debe495d317cc9c6fb10071f7fefd93100a4f \ + --hash=sha256:3d7954194c36e304e1523f55d7042c59dc53ec20dd4e9ea9d151f1b62b4415c0 \ + --hash=sha256:3ee8a80d67a4334482d9712b8e83ca6b1d9bc7e351931252ebef5d8f7335a547 \ + --hash=sha256:4093c631e96fdd49e0377a9c167bfd75b6d0bad2ace734c6eb20b348bc3ea180 \ + --hash=sha256:43395e90523f9c23a3d5bdf004733246fba087f2948f87ab28015f12359ca6a0 \ + --hash=sha256:43ce1b9935bfa1ede40028054d7f48b5469cd02733a365eec8a329ffd342915d \ + --hash=sha256:4410f84b33374409552ac9b6903507cdb31cd30d2501fc5ca13d18f73548444a \ + --hash=sha256:494994f807ba0b92092a163a0a283961369a65f6cbe01e8891132b7a320e61eb \ + --hash=sha256:4d4a848d1837973bf0f4b5e54e3bec977d99be36a7895c61abb659301b02c112 \ + --hash=sha256:4ed11165dd45ce798d99a136808a794a748d5dc38511303239d4e2363c0695dc \ + --hash=sha256:4f3607b129417e111e30637af1b56f24f7a49e64763253bbc275c75fa887d4b2 \ + --hash=sha256:510b5b1bfbe20e1a7b3baf5fed9e9451873559a976c1a78eebaa3b86c57b4265 \ + --hash=sha256:524f35912131cc2cabb00edfd8d573b07f2d9f21fa824bd3fb19725a9cf06327 \ + --hash=sha256:587ca6d3cef6e4e868102672d3bd9dc9698c309ba56d41c2b9c85bbb903cdb95 \ + --hash=sha256:58d4b711689366d4a03ac7957ab8c28890415e267f9b6589969e74b6e42225ec \ + --hash=sha256:5b3cc074004d968722f51e550b41a27be656ec48f8afaeeb45ebf65b561481dd \ + --hash=sha256:5dab0844f2cf82be357a0eb11a9087f70c5430b2c241493fc122bb6f2bb0917c \ + --hash=sha256:5e55da2c8724191e5b557f8e18943b1b4839b8efc3ef60d65985bcf6f587dd38 \ + --hash=sha256:5eeb539606f18a0b232d4ba45adccde4125592f3f636a6182b4a8a436548b914 \ + --hash=sha256:5f4d5ea15c9382135076d2fb28dde923352fe02951e66935a9efaac8f10e81b0 \ + --hash=sha256:5fb2ce4b8045c78ebbc7b8f3c15062e435d47e7393cc57c25115cfd49883747a \ + --hash=sha256:6172447e1b368dcbc458925e5ddaf9113477b0ed542df258d84fa28fc45ceea7 \ + --hash=sha256:6967ced6730aed543b8673008b5a391c3b1076d834ca438bbd70635c73775368 \ + --hash=sha256:6974f52a02321b36847cd19d1b8e381bf39939c21efd6ee2fc13a28b0d99348c \ + --hash=sha256:6c3020404e0b5eefd7c9485ccf8393cfb75ec38ce75586e046573c9dc29967a0 \ + --hash=sha256:6c6e0c425f22c1c719c42670d561ad682f7bfeeef918edea971a79ac5252437f \ + --hash=sha256:70051525001750221daa10907c77830bc889cb6d865cc0b813d9db7fefc21451 \ + --hash=sha256:7905193081db9bfa73b1219140b3d315831cbff0d8941f22da695832f0dd188f \ + --hash=sha256:7bc37c4d6b87fb1017ea28c9508b36bbcb0c3d18b4260fcdf08b200c74a6aee8 \ + --hash=sha256:7c4855522edb2e6ae7fdb58e07c3ba9111e7621a8956f481c68d5d979c93032e \ + --hash=sha256:7e4c4629ddad63006efa0ef968c8e4751c5868ff0b1c5c40f76524e894c50248 \ + --hash=sha256:7eedaa5d036d9336c95915035fb57422054014ebdeb6f3b42eac809928e40d0c \ + --hash=sha256:7f4bf76817c14aa98cc6697ac02f3972cb8c3da93e9ef16b9c66573a68014f91 \ + --hash=sha256:81de08ac11bcb85841e440c13611c00b67d3bf82698314928d0b676362546724 \ + --hash=sha256:832436e59afb93e1836081a20f324cb185836c617659b07b129141a8426973c7 \ + --hash=sha256:861bf317735688269936f755fa136a99d1ed526883859f86e41a5d43c61d8966 \ + --hash=sha256:87a3044c3a35055527ac75e419dfa9f4f3667a1e887ee80360589eb8c90aabb9 \ + --hash=sha256:890b5a14ce214389b2cc36ce82f3093f96f4cc730c1cffdbefff77a7c71f2a97 \ + --hash=sha256:89f4988c7203739d48c6f806f1e87a1d96e0806d44f0fba61dba81392c9e474d \ + --hash=sha256:8bf32b98b75c13ec7cf774164172683d6e7891088f6316e54425fde1efc276d5 \ + --hash=sha256:8dadd1314583ec0bf2d1379f7008ad627cd6336625d6679cf2f8e67081b83acf \ + --hash=sha256:901032ff242d479a0efa956d853d16875d42157f98951c0230f69e69f9c09bac \ + --hash=sha256:9011560a466d2eb3f5a6e4929cf4a09be405c64154e12df0dd72713f6500e32b \ + --hash=sha256:906bc3a79de8c4ae5b86d3d75a8b77e44404b0f4261714306e3ad248d8ab0951 \ + --hash=sha256:919e32f147ae93a09fe064d77d5ebf4e35502a8df75c29fb05788528e330fe74 \ + --hash=sha256:91d7cc2a76b5567591d12c01f019dd7afce6ba8cba6571187e21e2fc418ae648 \ + --hash=sha256:929811df5462e182b13920da56c6e0284af407d1de637d8e536c5cd00a7daf60 \ + --hash=sha256:949f3b7c29912693cee0afcf09acd6ebc04c57af949d9bf77d6101ebb61e388c \ + --hash=sha256:a090ca607cbb6a34b0391776f0cb48062081f5f60ddcce5d11838e67a01928d1 \ + --hash=sha256:a1fd8a29719ccce974d523580987b7f8229aeace506952fa9ce1d53a033873c8 \ + --hash=sha256:a37b8f0391212d29b3a91a799c8e4a2855e0576911cdfb2515487e30e322253d \ + --hash=sha256:a3daabb76a78f829cafc365531c972016e4aa8d5b4bf60660ad8ecee19df7ccc \ + --hash=sha256:a469274ad18dc0e4d316eefa616d1d0c2ff9da369af19fa6f3daa4f09671fd61 \ + --hash=sha256:a599669fd7c47233438a56936988a2478685e74854088ef5293802123b5b2460 \ + --hash=sha256:a743e5a28af5f70f9c080380a5f908d4d21d40e8f0e0c8901604d15cfa9ba751 \ + --hash=sha256:a77def80806c421b4b0af06f45d65a136e7ac0bdca3c09d9e2ea4e515367c7e9 \ + --hash=sha256:a7e53012d2853a07a4a79c00643832161a910674a893d296c9f1259859a289d2 \ + --hash=sha256:a93dde851926f4f2678e704fadeb39e16c35d8baebd5252c9fd94ce8ce68c4a0 \ + --hash=sha256:aac0411d20e345dc0920bdec5548e438e999ff68d77564d5e9463a7ca9d3e7b1 \ + --hash=sha256:ae15b066e5ad21366600ebec29a7ccbc86812ed267e4b28e860b8ca16a2bc474 \ + --hash=sha256:aea440a510e14e818e67bfc4027880e2fb500c2ccb20ab21c7a7c8b5b4703d75 \ + --hash=sha256:af6fa6817889314555aede9a919612b23739395ce767fe7fcbea9a80bf140fe5 \ + --hash=sha256:b760c65308ff1e462f65d69c12e4ae085cff3b332d894637f6273a12a482d09f \ + --hash=sha256:be36e3d172dc816333f33520154d708a2657ea63762ec16b62ece02ab5e4daf2 \ + --hash=sha256:c247dd99d39e0338a604f8c2b3bc7061d5c2e9e2ac7ba9cc1be5a69cb6cd832f \ + --hash=sha256:c5529b34c1c9d937168297f2c1fde7ebe9ebdd5e121297ff9c043bdb2ae3d6fb \ + --hash=sha256:c8146669223164fc87a7e3de9f81e9423c67a79d6b3447994dfb9c95da16e2d6 \ + --hash=sha256:c8fd5270e906eef71d4a8d19b7c6a43760c6abcfcc10c9101d14eb2357418de9 \ + --hash=sha256:ca63e1890ede90b2e4454f9a65135a4d387a4585ff8282bb72964fab893f2111 \ + --hash=sha256:caf9ee9a5775f3111642d33b86237b05808dafcd6268faa492250e9b78046eb2 \ + --hash=sha256:cb1dac1770878ade83f2ccdf7d25e494f05c9165f5246b46a621cc849341dc01 \ + --hash=sha256:cdad5b9014d83ca68c25d2e9444e28e967ef16e80f6b436918c700c117a85467 \ + --hash=sha256:cdbc1fc1bc0bff1cef838eafe581b55bfbffaed4ed0318b724d0b71d4d377619 \ + --hash=sha256:ceb64bbc6eac5a140ca649003756940f8d6a7c444a68af170b3187623b43bebf \ + --hash=sha256:d0c5516f0aed654134a2fc936325cc2e642f8a0e096d075209672eb321cff408 \ + --hash=sha256:d143fd47fad1db3d7c27a1b1d66162e855b5d50a89666af46e1679c496e8e579 \ + --hash=sha256:d192f0f30804e55db0d0e0a35d83a9fead0e9a359a9ed0285dbacea60cc10a84 \ + --hash=sha256:d2b35ca2c7f81d173d2fadc2f4f31e88cc5f7a39ae5b6db5513cf3383b0e0ec7 \ + --hash=sha256:d342778ef319e1026af243ed0a07c97acf3bad33b9f29e7ae6a1f68fd083e90c \ + --hash=sha256:d487f5432bf35b60ed625d7e1b448e2dc855422e87469e3f450aa5552b0eb284 \ + --hash=sha256:d7702622a8b40c49bffb46e1e3ba2e81268d5c04a34f460978c6b5517a34dd52 \ + --hash=sha256:db85ecf4e609a48f4b29055f1e144231b90edc90af7481aa731ba2d059226b1b \ + --hash=sha256:de6551e370ef19f8de1807d0a9aa2cdfdce2e85ce88b122fe9f6b2b076837e59 \ + --hash=sha256:e1140c64812cb9b06c922e77f1c26a75ec5e3f0fb2bf92cc8c58720dec276752 \ + --hash=sha256:e4fe605b917c70283db7dfe5ada75e04561479075761a0b3866c081d035b01c1 \ + --hash=sha256:e6a904cb26bfefc2f0a6f240bdf5233be78cd2488900a2f846f3c3ac8489ab80 \ + --hash=sha256:e79e6520141d792237c70bcd7a3b122d00f2613769ae0cb61c52e89fd3443839 \ + --hash=sha256:e84799f09591700a4154154cab9787452925578841a94321d5ee8fb9a9a328f0 \ + --hash=sha256:e93dfc1a1165e385cc8239fab7c036fb2cd8093728cbd85097b284d7b99249a2 \ + --hash=sha256:efa8b278894b14d6da122a72fefcebc28445f2d3f880ac59d46c90f4c13be9a3 \ + --hash=sha256:f0d8a7a6b5983c2496e364b969f0e526647a06b075d034f3297dc66f3b360c64 \ + --hash=sha256:f0db75f47be8b8abc8d9e31bc7aad0547ca26f24a54e6fd10231d623f183d089 \ + --hash=sha256:f296c40e23065d0d6650c4aefe7470d2a25fffda489bcc3eb66083f3ac9f6643 \ + --hash=sha256:f31859074d57b4639318523d6ffdca586ace54271a73ad23ad021acd807eb14b \ + --hash=sha256:f66b5337fa213f1da0d9000bc8dc0cb5b896b726eefd9c6046f699b169c41b9e \ + --hash=sha256:f733d788519c7e3e71f0855c96618720f5d3d60c3cb829d8bbb722dddce37985 \ + --hash=sha256:fce1473f3ccc4187f75b4690cfc922628aed4d3dd013d047f95a9b3919a86596 \ + --hash=sha256:fd5f17ff8f14003595ab414e45fce13d073e0762394f957182e69035c9f3d7c2 \ + --hash=sha256:fdc3ff3bfccdc6b9cc7c342c03aa2400683f0cb891d46e94b64a197910dc4064 # via fonttools -certifi==2025.1.31 +certifi==2024.12.14 \ + --hash=sha256:1275f7a45be9464efc1173084eaa30f866fe2e47d389406136d332ed4967ec56 \ + --hash=sha256:b650d30f370c2b724812bee08008be0c4163b163ddaec3f2546c1caf65f191db # via # requests # sentry-sdk -cffi==1.17.1 +cffi==1.17.1 \ + --hash=sha256:045d61c734659cc045141be4bae381a41d89b741f795af1dd018bfb532fd0df8 \ + --hash=sha256:0984a4925a435b1da406122d4d7968dd861c1385afe3b45ba82b750f229811e2 \ + --hash=sha256:0e2b1fac190ae3ebfe37b979cc1ce69c81f4e4fe5746bb401dca63a9062cdaf1 \ + --hash=sha256:0f048dcf80db46f0098ccac01132761580d28e28bc0f78ae0d58048063317e15 \ + --hash=sha256:1257bdabf294dceb59f5e70c64a3e2f462c30c7ad68092d01bbbfb1c16b1ba36 \ + --hash=sha256:1c39c6016c32bc48dd54561950ebd6836e1670f2ae46128f67cf49e789c52824 \ + --hash=sha256:1d599671f396c4723d016dbddb72fe8e0397082b0a77a4fab8028923bec050e8 \ + --hash=sha256:28b16024becceed8c6dfbc75629e27788d8a3f9030691a1dbf9821a128b22c36 \ + --hash=sha256:2bb1a08b8008b281856e5971307cc386a8e9c5b625ac297e853d36da6efe9c17 \ + --hash=sha256:30c5e0cb5ae493c04c8b42916e52ca38079f1b235c2f8ae5f4527b963c401caf \ + --hash=sha256:31000ec67d4221a71bd3f67df918b1f88f676f1c3b535a7eb473255fdc0b83fc \ + --hash=sha256:386c8bf53c502fff58903061338ce4f4950cbdcb23e2902d86c0f722b786bbe3 \ + --hash=sha256:3edc8d958eb099c634dace3c7e16560ae474aa3803a5df240542b305d14e14ed \ + --hash=sha256:45398b671ac6d70e67da8e4224a065cec6a93541bb7aebe1b198a61b58c7b702 \ + --hash=sha256:46bf43160c1a35f7ec506d254e5c890f3c03648a4dbac12d624e4490a7046cd1 \ + --hash=sha256:4ceb10419a9adf4460ea14cfd6bc43d08701f0835e979bf821052f1805850fe8 \ + --hash=sha256:51392eae71afec0d0c8fb1a53b204dbb3bcabcb3c9b807eedf3e1e6ccf2de903 \ + --hash=sha256:5da5719280082ac6bd9aa7becb3938dc9f9cbd57fac7d2871717b1feb0902ab6 \ + --hash=sha256:610faea79c43e44c71e1ec53a554553fa22321b65fae24889706c0a84d4ad86d \ + --hash=sha256:636062ea65bd0195bc012fea9321aca499c0504409f413dc88af450b57ffd03b \ + --hash=sha256:6883e737d7d9e4899a8a695e00ec36bd4e5e4f18fabe0aca0efe0a4b44cdb13e \ + --hash=sha256:6b8b4a92e1c65048ff98cfe1f735ef8f1ceb72e3d5f0c25fdb12087a23da22be \ + --hash=sha256:6f17be4345073b0a7b8ea599688f692ac3ef23ce28e5df79c04de519dbc4912c \ + --hash=sha256:706510fe141c86a69c8ddc029c7910003a17353970cff3b904ff0686a5927683 \ + --hash=sha256:72e72408cad3d5419375fc87d289076ee319835bdfa2caad331e377589aebba9 \ + --hash=sha256:733e99bc2df47476e3848417c5a4540522f234dfd4ef3ab7fafdf555b082ec0c \ + --hash=sha256:7596d6620d3fa590f677e9ee430df2958d2d6d6de2feeae5b20e82c00b76fbf8 \ + --hash=sha256:78122be759c3f8a014ce010908ae03364d00a1f81ab5c7f4a7a5120607ea56e1 \ + --hash=sha256:805b4371bf7197c329fcb3ead37e710d1bca9da5d583f5073b799d5c5bd1eee4 \ + --hash=sha256:85a950a4ac9c359340d5963966e3e0a94a676bd6245a4b55bc43949eee26a655 \ + --hash=sha256:8f2cdc858323644ab277e9bb925ad72ae0e67f69e804f4898c070998d50b1a67 \ + --hash=sha256:9755e4345d1ec879e3849e62222a18c7174d65a6a92d5b346b1863912168b595 \ + --hash=sha256:98e3969bcff97cae1b2def8ba499ea3d6f31ddfdb7635374834cf89a1a08ecf0 \ + --hash=sha256:a08d7e755f8ed21095a310a693525137cfe756ce62d066e53f502a83dc550f65 \ + --hash=sha256:a1ed2dd2972641495a3ec98445e09766f077aee98a1c896dcb4ad0d303628e41 \ + --hash=sha256:a24ed04c8ffd54b0729c07cee15a81d964e6fee0e3d4d342a27b020d22959dc6 \ + --hash=sha256:a45e3c6913c5b87b3ff120dcdc03f6131fa0065027d0ed7ee6190736a74cd401 \ + --hash=sha256:a9b15d491f3ad5d692e11f6b71f7857e7835eb677955c00cc0aefcd0669adaf6 \ + --hash=sha256:ad9413ccdeda48c5afdae7e4fa2192157e991ff761e7ab8fdd8926f40b160cc3 \ + --hash=sha256:b2ab587605f4ba0bf81dc0cb08a41bd1c0a5906bd59243d56bad7668a6fc6c16 \ + --hash=sha256:b62ce867176a75d03a665bad002af8e6d54644fad99a3c70905c543130e39d93 \ + --hash=sha256:c03e868a0b3bc35839ba98e74211ed2b05d2119be4e8a0f224fba9384f1fe02e \ + --hash=sha256:c59d6e989d07460165cc5ad3c61f9fd8f1b4796eacbd81cee78957842b834af4 \ + --hash=sha256:c7eac2ef9b63c79431bc4b25f1cd649d7f061a28808cbc6c47b534bd789ef964 \ + --hash=sha256:c9c3d058ebabb74db66e431095118094d06abf53284d9c81f27300d0e0d8bc7c \ + --hash=sha256:ca74b8dbe6e8e8263c0ffd60277de77dcee6c837a3d0881d8c1ead7268c9e576 \ + --hash=sha256:caaf0640ef5f5517f49bc275eca1406b0ffa6aa184892812030f04c2abf589a0 \ + --hash=sha256:cdf5ce3acdfd1661132f2a9c19cac174758dc2352bfe37d98aa7512c6b7178b3 \ + --hash=sha256:d016c76bdd850f3c626af19b0542c9677ba156e4ee4fccfdd7848803533ef662 \ + --hash=sha256:d01b12eeeb4427d3110de311e1774046ad344f5b1a7403101878976ecd7a10f3 \ + --hash=sha256:d63afe322132c194cf832bfec0dc69a99fb9bb6bbd550f161a49e9e855cc78ff \ + --hash=sha256:da95af8214998d77a98cc14e3a3bd00aa191526343078b530ceb0bd710fb48a5 \ + --hash=sha256:dd398dbc6773384a17fe0d3e7eeb8d1a21c2200473ee6806bb5e6a8e62bb73dd \ + --hash=sha256:de2ea4b5833625383e464549fec1bc395c1bdeeb5f25c4a3a82b5a8c756ec22f \ + --hash=sha256:de55b766c7aa2e2a3092c51e0483d700341182f08e67c63630d5b6f200bb28e5 \ + --hash=sha256:df8b1c11f177bc2313ec4b2d46baec87a5f3e71fc8b45dab2ee7cae86d9aba14 \ + --hash=sha256:e03eab0a8677fa80d646b5ddece1cbeaf556c313dcfac435ba11f107ba117b5d \ + --hash=sha256:e221cf152cff04059d011ee126477f0d9588303eb57e88923578ace7baad17f9 \ + --hash=sha256:e31ae45bc2e29f6b2abd0de1cc3b9d5205aa847cafaecb8af1476a609a2f6eb7 \ + --hash=sha256:edae79245293e15384b51f88b00613ba9f7198016a5948b5dddf4917d4d26382 \ + --hash=sha256:f1e22e8c4419538cb197e4dd60acc919d7696e5ef98ee4da4e01d3f8cfa4cc5a \ + --hash=sha256:f3a2b4222ce6b60e2e8b337bb9596923045681d71e5a082783484d845390938e \ + --hash=sha256:f6a16c31041f09ead72d69f583767292f750d24913dadacf5756b966aacb3f1a \ + --hash=sha256:f75c7ab1f9e4aca5414ed4d8e5c0e303a34f4421f8a0d47a4d019ceff0ab6af4 \ + --hash=sha256:f79fc4fc25f1c8698ff97788206bb3c2598949bfe0fef03d299eb1b5356ada99 \ + --hash=sha256:f7f5baafcc48261359e14bcd6d9bff6d4b28d9103847c9e136694cb0501aef87 \ + --hash=sha256:fc48c783f9c87e60831201f2cce7f3b2e4846bf4d8728eabe54d60700b318a0b # via # cryptography # weasyprint -charset-normalizer==3.4.1 +charset-normalizer==3.4.1 \ + --hash=sha256:0167ddc8ab6508fe81860a57dd472b2ef4060e8d378f0cc555707126830f2537 \ + --hash=sha256:01732659ba9b5b873fc117534143e4feefecf3b2078b0a6a2e925271bb6f4cfa \ + --hash=sha256:01ad647cdd609225c5350561d084b42ddf732f4eeefe6e678765636791e78b9a \ + --hash=sha256:04432ad9479fa40ec0f387795ddad4437a2b50417c69fa275e212933519ff294 \ + --hash=sha256:0907f11d019260cdc3f94fbdb23ff9125f6b5d1039b76003b5b0ac9d6a6c9d5b \ + --hash=sha256:0924e81d3d5e70f8126529951dac65c1010cdf117bb75eb02dd12339b57749dd \ + --hash=sha256:09b26ae6b1abf0d27570633b2b078a2a20419c99d66fb2823173d73f188ce601 \ + --hash=sha256:09b5e6733cbd160dcc09589227187e242a30a49ca5cefa5a7edd3f9d19ed53fd \ + --hash=sha256:0af291f4fe114be0280cdd29d533696a77b5b49cfde5467176ecab32353395c4 \ + --hash=sha256:0f55e69f030f7163dffe9fd0752b32f070566451afe180f99dbeeb81f511ad8d \ + --hash=sha256:1a2bc9f351a75ef49d664206d51f8e5ede9da246602dc2d2726837620ea034b2 \ + --hash=sha256:22e14b5d70560b8dd51ec22863f370d1e595ac3d024cb8ad7d308b4cd95f8313 \ + --hash=sha256:234ac59ea147c59ee4da87a0c0f098e9c8d169f4dc2a159ef720f1a61bbe27cd \ + --hash=sha256:2369eea1ee4a7610a860d88f268eb39b95cb588acd7235e02fd5a5601773d4fa \ + --hash=sha256:237bdbe6159cff53b4f24f397d43c6336c6b0b42affbe857970cefbb620911c8 \ + --hash=sha256:28bf57629c75e810b6ae989f03c0828d64d6b26a5e205535585f96093e405ed1 \ + --hash=sha256:2967f74ad52c3b98de4c3b32e1a44e32975e008a9cd2a8cc8966d6a5218c5cb2 \ + --hash=sha256:2a75d49014d118e4198bcee5ee0a6f25856b29b12dbf7cd012791f8a6cc5c496 \ + --hash=sha256:2bdfe3ac2e1bbe5b59a1a63721eb3b95fc9b6817ae4a46debbb4e11f6232428d \ + --hash=sha256:2d074908e1aecee37a7635990b2c6d504cd4766c7bc9fc86d63f9c09af3fa11b \ + --hash=sha256:2fb9bd477fdea8684f78791a6de97a953c51831ee2981f8e4f583ff3b9d9687e \ + --hash=sha256:311f30128d7d333eebd7896965bfcfbd0065f1716ec92bd5638d7748eb6f936a \ + --hash=sha256:329ce159e82018d646c7ac45b01a430369d526569ec08516081727a20e9e4af4 \ + --hash=sha256:345b0426edd4e18138d6528aed636de7a9ed169b4aaf9d61a8c19e39d26838ca \ + --hash=sha256:363e2f92b0f0174b2f8238240a1a30142e3db7b957a5dd5689b0e75fb717cc78 \ + --hash=sha256:3a3bd0dcd373514dcec91c411ddb9632c0d7d92aed7093b8c3bbb6d69ca74408 \ + --hash=sha256:3bed14e9c89dcb10e8f3a29f9ccac4955aebe93c71ae803af79265c9ca5644c5 \ + --hash=sha256:44251f18cd68a75b56585dd00dae26183e102cd5e0f9f1466e6df5da2ed64ea3 \ + --hash=sha256:44ecbf16649486d4aebafeaa7ec4c9fed8b88101f4dd612dcaf65d5e815f837f \ + --hash=sha256:4532bff1b8421fd0a320463030c7520f56a79c9024a4e88f01c537316019005a \ + --hash=sha256:49402233c892a461407c512a19435d1ce275543138294f7ef013f0b63d5d3765 \ + --hash=sha256:4c0907b1928a36d5a998d72d64d8eaa7244989f7aaaf947500d3a800c83a3fd6 \ + --hash=sha256:4d86f7aff21ee58f26dcf5ae81a9addbd914115cdebcbb2217e4f0ed8982e146 \ + --hash=sha256:5777ee0881f9499ed0f71cc82cf873d9a0ca8af166dfa0af8ec4e675b7df48e6 \ + --hash=sha256:5df196eb874dae23dcfb968c83d4f8fdccb333330fe1fc278ac5ceeb101003a9 \ + --hash=sha256:619a609aa74ae43d90ed2e89bdd784765de0a25ca761b93e196d938b8fd1dbbd \ + --hash=sha256:6e27f48bcd0957c6d4cb9d6fa6b61d192d0b13d5ef563e5f2ae35feafc0d179c \ + --hash=sha256:6ff8a4a60c227ad87030d76e99cd1698345d4491638dfa6673027c48b3cd395f \ + --hash=sha256:73d94b58ec7fecbc7366247d3b0b10a21681004153238750bb67bd9012414545 \ + --hash=sha256:7461baadb4dc00fd9e0acbe254e3d7d2112e7f92ced2adc96e54ef6501c5f176 \ + --hash=sha256:75832c08354f595c760a804588b9357d34ec00ba1c940c15e31e96d902093770 \ + --hash=sha256:7709f51f5f7c853f0fb938bcd3bc59cdfdc5203635ffd18bf354f6967ea0f824 \ + --hash=sha256:78baa6d91634dfb69ec52a463534bc0df05dbd546209b79a3880a34487f4b84f \ + --hash=sha256:7974a0b5ecd505609e3b19742b60cee7aa2aa2fb3151bc917e6e2646d7667dcf \ + --hash=sha256:7a4f97a081603d2050bfaffdefa5b02a9ec823f8348a572e39032caa8404a487 \ + --hash=sha256:7b1bef6280950ee6c177b326508f86cad7ad4dff12454483b51d8b7d673a2c5d \ + --hash=sha256:7d053096f67cd1241601111b698f5cad775f97ab25d81567d3f59219b5f1adbd \ + --hash=sha256:804a4d582ba6e5b747c625bf1255e6b1507465494a40a2130978bda7b932c90b \ + --hash=sha256:807f52c1f798eef6cf26beb819eeb8819b1622ddfeef9d0977a8502d4db6d534 \ + --hash=sha256:80ed5e856eb7f30115aaf94e4a08114ccc8813e6ed1b5efa74f9f82e8509858f \ + --hash=sha256:8417cb1f36cc0bc7eaba8ccb0e04d55f0ee52df06df3ad55259b9a323555fc8b \ + --hash=sha256:8436c508b408b82d87dc5f62496973a1805cd46727c34440b0d29d8a2f50a6c9 \ + --hash=sha256:89149166622f4db9b4b6a449256291dc87a99ee53151c74cbd82a53c8c2f6ccd \ + --hash=sha256:8bfa33f4f2672964266e940dd22a195989ba31669bd84629f05fab3ef4e2d125 \ + --hash=sha256:8c60ca7339acd497a55b0ea5d506b2a2612afb2826560416f6894e8b5770d4a9 \ + --hash=sha256:91b36a978b5ae0ee86c394f5a54d6ef44db1de0815eb43de826d41d21e4af3de \ + --hash=sha256:955f8851919303c92343d2f66165294848d57e9bba6cf6e3625485a70a038d11 \ + --hash=sha256:97f68b8d6831127e4787ad15e6757232e14e12060bec17091b85eb1486b91d8d \ + --hash=sha256:9b23ca7ef998bc739bf6ffc077c2116917eabcc901f88da1b9856b210ef63f35 \ + --hash=sha256:9f0b8b1c6d84c8034a44893aba5e767bf9c7a211e313a9605d9c617d7083829f \ + --hash=sha256:aabfa34badd18f1da5ec1bc2715cadc8dca465868a4e73a0173466b688f29dda \ + --hash=sha256:ab36c8eb7e454e34e60eb55ca5d241a5d18b2c6244f6827a30e451c42410b5f7 \ + --hash=sha256:b010a7a4fd316c3c484d482922d13044979e78d1861f0e0650423144c616a46a \ + --hash=sha256:b1ac5992a838106edb89654e0aebfc24f5848ae2547d22c2c3f66454daa11971 \ + --hash=sha256:b7b2d86dd06bfc2ade3312a83a5c364c7ec2e3498f8734282c6c3d4b07b346b8 \ + --hash=sha256:b97e690a2118911e39b4042088092771b4ae3fc3aa86518f84b8cf6888dbdb41 \ + --hash=sha256:bc2722592d8998c870fa4e290c2eec2c1569b87fe58618e67d38b4665dfa680d \ + --hash=sha256:c0429126cf75e16c4f0ad00ee0eae4242dc652290f940152ca8c75c3a4b6ee8f \ + --hash=sha256:c30197aa96e8eed02200a83fba2657b4c3acd0f0aa4bdc9f6c1af8e8962e0757 \ + --hash=sha256:c4c3e6da02df6fa1410a7680bd3f63d4f710232d3139089536310d027950696a \ + --hash=sha256:c75cb2a3e389853835e84a2d8fb2b81a10645b503eca9bcb98df6b5a43eb8886 \ + --hash=sha256:c96836c97b1238e9c9e3fe90844c947d5afbf4f4c92762679acfe19927d81d77 \ + --hash=sha256:d7f50a1f8c450f3925cb367d011448c39239bb3eb4117c36a6d354794de4ce76 \ + --hash=sha256:d973f03c0cb71c5ed99037b870f2be986c3c05e63622c017ea9816881d2dd247 \ + --hash=sha256:d98b1668f06378c6dbefec3b92299716b931cd4e6061f3c875a71ced1780ab85 \ + --hash=sha256:d9c3cdf5390dcd29aa8056d13e8e99526cda0305acc038b96b30352aff5ff2bb \ + --hash=sha256:dad3e487649f498dd991eeb901125411559b22e8d7ab25d3aeb1af367df5efd7 \ + --hash=sha256:dccbe65bd2f7f7ec22c4ff99ed56faa1e9f785482b9bbd7c717e26fd723a1d1e \ + --hash=sha256:dd78cfcda14a1ef52584dbb008f7ac81c1328c0f58184bf9a84c49c605002da6 \ + --hash=sha256:e218488cd232553829be0664c2292d3af2eeeb94b32bea483cf79ac6a694e037 \ + --hash=sha256:e358e64305fe12299a08e08978f51fc21fac060dcfcddd95453eabe5b93ed0e1 \ + --hash=sha256:ea0d8d539afa5eb2728aa1932a988a9a7af94f18582ffae4bc10b3fbdad0626e \ + --hash=sha256:eab677309cdb30d047996b36d34caeda1dc91149e4fdca0b1a039b3f79d9a807 \ + --hash=sha256:eb8178fe3dba6450a3e024e95ac49ed3400e506fd4e9e5c32d30adda88cbd407 \ + --hash=sha256:ecddf25bee22fe4fe3737a399d0d177d72bc22be6913acfab364b40bce1ba83c \ + --hash=sha256:eea6ee1db730b3483adf394ea72f808b6e18cf3cb6454b4d86e04fa8c4327a12 \ + --hash=sha256:f08ff5e948271dc7e18a35641d2f11a4cd8dfd5634f55228b691e62b37125eb3 \ + --hash=sha256:f30bf9fd9be89ecb2360c7d94a711f00c09b976258846efe40db3d05828e8089 \ + --hash=sha256:fa88b843d6e211393a37219e6a1c1df99d35e8fd90446f1118f4216e307e48cd \ + --hash=sha256:fc54db6c8593ef7d4b2a331b58653356cf04f67c960f584edb7c3d8c97e8f39e \ + --hash=sha256:fd4ec41f914fa74ad1b8304bbc634b3de73d2a0889bd32076342a573e0779e00 \ + --hash=sha256:ffc9202a29ab3920fa812879e95a9e78b2465fd10be7fcbd042899695d75e616 # via requests -coreapi==2.3.3 +coreapi==2.3.3 \ + --hash=sha256:46145fcc1f7017c076a2ef684969b641d18a2991051fddec9458ad3f78ffc1cb \ + --hash=sha256:bf39d118d6d3e171f10df9ede5666f63ad80bba9a29a8ec17726a66cf52ee6f3 # via -r src/backend/requirements.in -coreschema==0.0.4 +coreschema==0.0.4 \ + --hash=sha256:5e6ef7bf38c1525d5e55a895934ab4273548629f16aed5c0a6caa74ebf45551f \ + --hash=sha256:9503506007d482ab0867ba14724b93c18a33b22b6d19fb419ef2d239dd4a1607 # via coreapi -cryptography==43.0.3 +cryptography==43.0.3 \ + --hash=sha256:0c580952eef9bf68c4747774cde7ec1d85a6e61de97281f2dba83c7d2c806362 \ + --hash=sha256:0f996e7268af62598f2fc1204afa98a3b5712313a55c4c9d434aef49cadc91d4 \ + --hash=sha256:1ec0bcf7e17c0c5669d881b1cd38c4972fade441b27bda1051665faaa89bdcaa \ + --hash=sha256:281c945d0e28c92ca5e5930664c1cefd85efe80e5c0d2bc58dd63383fda29f83 \ + --hash=sha256:2ce6fae5bdad59577b44e4dfed356944fbf1d925269114c28be377692643b4ff \ + --hash=sha256:315b9001266a492a6ff443b61238f956b214dbec9910a081ba5b6646a055a805 \ + --hash=sha256:443c4a81bb10daed9a8f334365fe52542771f25aedaf889fd323a853ce7377d6 \ + --hash=sha256:4a02ded6cd4f0a5562a8887df8b3bd14e822a90f97ac5e544c162899bc467664 \ + --hash=sha256:53a583b6637ab4c4e3591a15bc9db855b8d9dee9a669b550f311480acab6eb08 \ + --hash=sha256:63efa177ff54aec6e1c0aefaa1a241232dcd37413835a9b674b6e3f0ae2bfd3e \ + --hash=sha256:74f57f24754fe349223792466a709f8e0c093205ff0dca557af51072ff47ab18 \ + --hash=sha256:7e1ce50266f4f70bf41a2c6dc4358afadae90e2a1e5342d3c08883df1675374f \ + --hash=sha256:81ef806b1fef6b06dcebad789f988d3b37ccaee225695cf3e07648eee0fc6b73 \ + --hash=sha256:846da004a5804145a5f441b8530b4bf35afbf7da70f82409f151695b127213d5 \ + --hash=sha256:8ac43ae87929a5982f5948ceda07001ee5e83227fd69cf55b109144938d96984 \ + --hash=sha256:9762ea51a8fc2a88b70cf2995e5675b38d93bf36bd67d91721c309df184f49bd \ + --hash=sha256:a2a431ee15799d6db9fe80c82b055bae5a752bef645bba795e8e52687c69efe3 \ + --hash=sha256:bf7a1932ac4176486eab36a19ed4c0492da5d97123f1406cf15e41b05e787d2e \ + --hash=sha256:c2e6fc39c4ab499049df3bdf567f768a723a5e8464816e8f009f121a5a9f4405 \ + --hash=sha256:cbeb489927bd7af4aa98d4b261af9a5bc025bd87f0e3547e11584be9e9427be2 \ + --hash=sha256:d03b5621a135bffecad2c73e9f4deb1a0f977b9a8ffe6f8e002bf6c9d07b918c \ + --hash=sha256:d56e96520b1020449bbace2b78b603442e7e378a9b3bd68de65c782db1507995 \ + --hash=sha256:df6b6c6d742395dd77a23ea3728ab62f98379eff8fb61be2744d4679ab678f73 \ + --hash=sha256:e1be4655c7ef6e1bbe6b5d0403526601323420bcf414598955968c9ef3eb7d16 \ + --hash=sha256:f18c716be16bc1fea8e95def49edf46b82fccaa88587a45f8dc0ff6ab5d8e0a7 \ + --hash=sha256:f46304d6f0c6ab8e52770addfa2fc41e6629495548862279641972b6215451cd \ + --hash=sha256:f7b178f11ed3664fd0e995a47ed2b5ff0a12d893e41dd0494f406d1cf555cab7 # via # -r src/backend/requirements.in # djangorestframework-simplejwt # fido2 # pyjwt -cssselect2==0.7.0 +cssselect2==0.7.0 \ + --hash=sha256:1ccd984dab89fc68955043aca4e1b03e0cf29cad9880f6e28e3ba7a74b14aa5a \ + --hash=sha256:fd23a65bfd444595913f02fc71f6b286c29261e354c41d722ca7a261a49b5969 # via weasyprint -defusedxml==0.7.1 +defusedxml==0.7.1 \ + --hash=sha256:1bb3032db185915b62d7c6209c5a8792be6a32ab2fedacc84e01b52c51aa3e69 \ + --hash=sha256:a352e7e428770286cc899e2542b6cdaedb2b4953ff269a210103ec58f6198a61 # via python3-openid -deprecated==1.2.18 +deprecated==1.2.15 \ + --hash=sha256:353bc4a8ac4bfc96800ddab349d89c25dec1079f65fd53acdcc1e0b975b21320 \ + --hash=sha256:683e561a90de76239796e6b6feac66b99030d2dd3fcf61ef996330f14bbb9b0d # via # opentelemetry-api # opentelemetry-exporter-otlp-proto-grpc # opentelemetry-exporter-otlp-proto-http # opentelemetry-semantic-conventions -django==4.2.18 +django==4.2.18 \ + --hash=sha256:52ae8eacf635617c0f13b44f749e5ea13dc34262819b2cc8c8636abb08d82c4b \ + --hash=sha256:ba52eff7e228f1c775d5b0db2ba53d8c49d2f8bfe6ca0234df6b7dd12fb25b19 # via # -r src/backend/requirements.in # django-allauth @@ -79,131 +414,578 @@ django==4.2.18 # drf-spectacular django-allauth[mfa, openid, saml, socialaccount] @ git+https://codeberg.org/allauth/django-allauth@3428a0e202e996d62d919a1e458f34131a77a640 # via -r src/backend/requirements.in -django-cleanup==9.0.0 +django-cleanup==9.0.0 \ + --hash=sha256:19f8b0e830233f9f0f683b17181f414672a0f48afe3ea3cc80ba47ae40ad880c \ + --hash=sha256:bb9fb560aaf62959c81e31fa40885c36bbd5854d5aa21b90df2c7e4ba633531e # via -r src/backend/requirements.in -django-cors-headers==4.6.0 +django-cors-headers==4.6.0 \ + --hash=sha256:14d76b4b4c8d39375baeddd89e4f08899051eeaf177cb02a29bd6eae8cf63aa8 \ + --hash=sha256:8edbc0497e611c24d5150e0055d3b178c6534b8ed826fb6f53b21c63f5d48ba3 # via -r src/backend/requirements.in -django-dbbackup==4.2.1 +django-dbbackup==4.2.1 \ + --hash=sha256:157a2ec10d482345cd75092e510ac40d6e2ee6084604a1d17abe178c2f06bc69 \ + --hash=sha256:b23265600ead0780ca781b1b4b594949aaa8a20d74f08701f91ee9d7eb1f08cd # via -r src/backend/requirements.in -django-error-report-2==0.4.2 +django-error-report-2==0.4.2 \ + --hash=sha256:1dd99c497af09b7ea99f5fbaf910501838150a9d5390796ea00e187bc62f6c1b \ + --hash=sha256:603e1e3b24d01bbfeab6379af948893b2b034031c80fa8b45cf1c4735341c04b # via -r src/backend/requirements.in -django-filter==24.3 +django-filter==24.3 \ + --hash=sha256:c4852822928ce17fb699bcfccd644b3574f1a2d80aeb2b4ff4f16b02dd49dc64 \ + --hash=sha256:d8ccaf6732afd21ca0542f6733b11591030fa98669f8d15599b358e24a2cd9c3 # via -r src/backend/requirements.in -django-flags==5.0.13 +django-flags==5.0.13 \ + --hash=sha256:52df74b86d93f5cb402190ad26b68a5ba0f127e9e016189f1a6f2e8ba3c06a42 \ + --hash=sha256:ff6940cf37e07d6d0c4ac28c5420c8cfc478b62541473dba4aa02d600f7db9fc # via -r src/backend/requirements.in -django-ical==1.9.2 +django-ical==1.9.2 \ + --hash=sha256:44c9b6fa90d09f25e9ebaa91ed9eb007f079afbc23d6aac909cfc18188a8e90c \ + --hash=sha256:74a16bca05735f91a00120cad7250f3c3aa292a9f698a6cfdc544a922c11de70 # via -r src/backend/requirements.in -django-ipware==7.0.1 +django-ipware==7.0.1 \ + --hash=sha256:d9ec43d2bf7cdf216fed8d494a084deb5761a54860a53b2e74346a4f384cff47 \ + --hash=sha256:db16bbee920f661ae7f678e4270460c85850f03c6761a4eaeb489bdc91f64709 # via django-structlog -django-js-asset==2.2.0 +django-js-asset==2.2.0 \ + --hash=sha256:0c57a82cae2317e83951d956110ce847f58ff0cdc24e314dbc18b35033917e94 \ + --hash=sha256:7ef3e858e13d06f10799b56eea62b1e76706f42cf4e709be4e13356bc0ae30d8 # via django-mptt -django-maintenance-mode==0.21.1 +django-maintenance-mode==0.21.1 \ + --hash=sha256:b79afddb671c59972ae542e4fafbc99117d2d37991843eaaa837e328eed12b1b \ + --hash=sha256:c02fff0e386b7f8b2ab54479d3a0d336ae34014da22a7a2365ca96d5a2c1db94 # via -r src/backend/requirements.in -django-markdownify==0.9.5 +django-markdownify==0.9.5 \ + --hash=sha256:2c4ae44e386c209453caf5e9ea1b74f64535985d338ad2d5ad5e7089cc94be86 \ + --hash=sha256:34c34eba4a797282a5c5bd97b13cec84d6a4c0673ad47ce1c1d000d74dd8d4ab # via -r src/backend/requirements.in -django-money==3.2.0 +django-money==3.2.0 \ + --hash=sha256:2e4174b47993780bf4b61ad3fa0a66ebe140da42fdbe68b628c7ba9788287214 \ + --hash=sha256:3099f906407175af06b56ef3ff5c250e2fc525ff00f50d42f77b98597e625459 # via -r src/backend/requirements.in -django-mptt==0.16.0 +django-mptt==0.16.0 \ + --hash=sha256:56c9606bf0b329b5f5afd55dd8bfd073612ea1d5999b10903b09de62bee84c8e \ + --hash=sha256:8716849ba3318d94e2e100ed0923a05c1ffdf8195f8472b690dbaf737d2af3b5 # via -r src/backend/requirements.in -django-otp==1.3.0 +django-otp==1.3.0 \ + --hash=sha256:5277731bc05b6cdbf96aa84ac46018e30ed5fb248086053b0146f925de059060 \ + --hash=sha256:8f4156a3c14ce2aaa31379385eadf388925cd50fc4b5d20a3b944f454c98ff7c # via -r src/backend/requirements.in -django-picklefield==3.2 +django-picklefield==3.2 \ + --hash=sha256:aa463f5d79d497dbe789f14b45180f00a51d0d670067d0729f352a3941cdfa4d \ + --hash=sha256:e9a73539d110f69825d9320db18bcb82e5189ff48dbed41821c026a20497764c # via django-q2 -django-q-sentry==0.1.6 +django-q-sentry==0.1.6 \ + --hash=sha256:9b8b4d7fad253a7d9a47f2c2ab0d9dea83078b7ef45c8849dbb1e4176ef8d050 # via -r src/backend/requirements.in -django-q2==1.7.6 +django-q2==1.7.6 \ + --hash=sha256:5210b121573cf65b97d495dbebefe6cfac394d8c0aec9ca2117e8e56e2fda17d \ + --hash=sha256:9060f4d68e1f3a8a748e0ebd0bd83c8c24bc13036105035873faab9d85b0e8f6 # via -r src/backend/requirements.in -django-recurrence==1.11.1 +django-recurrence==1.11.1 \ + --hash=sha256:0c65f30872599b5813a9bab6952dada23c55894f28674490a753ada559f14bc5 \ + --hash=sha256:9c89444e651a78c587f352c5f63eda48ab2f53996347b9fcdff2d248f4fcff70 # via django-ical -django-redis==5.4.0 +django-redis==5.4.0 \ + --hash=sha256:6a02abaa34b0fea8bf9b707d2c363ab6adc7409950b2db93602e6cb292818c42 \ + --hash=sha256:ebc88df7da810732e2af9987f7f426c96204bf89319df4c6da6ca9a2942edd5b # via -r src/backend/requirements.in -django-sesame==3.2.2 +django-sesame==3.2.2 \ + --hash=sha256:523ebd4d04e28c897c262f25b78b6fd8f37e11cdca6e277fdc8bf496bd686cf5 \ + --hash=sha256:5d753a309166356b6a0d7fc047690943b9e80b4aa7952f1a6400fe6ce60d573c # via -r src/backend/requirements.in -django-sql-utils==0.7.0 +django-sql-utils==0.7.0 \ + --hash=sha256:9371ff28eaf326836a7c52887259123cdd3fbffb7b738e42ae1a21258be0feb6 \ + --hash=sha256:fefc40c826896b60fcf33e35b6e30b523fc958955a16006438cd3ba6d795a532 # via -r src/backend/requirements.in -django-sslserver==0.22 +django-sslserver==0.22 \ + --hash=sha256:c598a363d2ccdc2421c08ddb3d8b0973f80e8e47a3a5b74e4a2896f21c2947c5 # via -r src/backend/requirements.in -django-stdimage==6.0.2 +django-stdimage==6.0.2 \ + --hash=sha256:880ab14828be56b53f711c3afae83c219ddd5d9af00850626736feb48382bf7f \ + --hash=sha256:9a73f7da48c48074580e2b032d5bdb7164935dbe4b9dc4fb88a7e112f3d521c8 # via -r src/backend/requirements.in -django-structlog==9.0.1 +django-structlog==9.0.0 \ + --hash=sha256:0ada1ac0fa4e90a499fc9ce22fc164830304e7219cab12d3b5ef9c2f2967ff5b \ + --hash=sha256:8c32097b3f09b2727b746d6cdf4f50565c62fd040b3a0c98cb731de0b380b854 # via -r src/backend/requirements.in -django-taggit==6.1.0 +django-taggit==6.1.0 \ + --hash=sha256:ab776264bbc76cb3d7e49e1bf9054962457831bd21c3a42db9138b41956e4cf0 \ + --hash=sha256:c4d1199e6df34125dd36db5eb0efe545b254dec3980ce5dd80e6bab3e78757c3 # via -r src/backend/requirements.in -django-weasyprint==2.3.1 +django-weasyprint==2.3.1 \ + --hash=sha256:09cc1c40c92db34bed80154be7c959fea03d6001dc46fd599f3fd464d6a6dc72 \ + --hash=sha256:cd35b8bd24b28128a17a2416d0e6f3e64cb727f25c53467150b4be16ccd01c19 # via -r src/backend/requirements.in -django-xforwardedfor-middleware==2.0 +django-xforwardedfor-middleware==2.0 \ + --hash=sha256:16fd1cb27f33a5541b6f3e0b43afb1b7334a76f27a1255b69e14ec5c440f0b24 # via -r src/backend/requirements.in -djangorestframework==3.14.0 +djangorestframework==3.14.0 \ + --hash=sha256:579a333e6256b09489cbe0a067e66abe55c6595d8926be6b99423786334350c8 \ + --hash=sha256:eb63f58c9f218e1a7d064d17a70751f528ed4e1d35547fdade9aaf4cd103fd08 # via # -r src/backend/requirements.in # djangorestframework-simplejwt # drf-spectacular -djangorestframework-simplejwt[crypto]==5.4.0 +djangorestframework-simplejwt[crypto]==5.4.0 \ + --hash=sha256:7aec953db9ed4163430c16d086eecb0f028f814ce6bba62b06c25919261e9077 \ + --hash=sha256:cccecce1a0e1a4a240fae80da73e5fc23055bababb8b67de88fa47cd36822320 # via -r src/backend/requirements.in -docutils==0.21.2 +docutils==0.21.2 \ + --hash=sha256:3a6b18732edf182daa3cd12775bbb338cf5691468f91eeeb109deff6ebfa986f \ + --hash=sha256:dafca5b9e384f0e419294eb4d2ff9fa826435bf15f15b7bd45723e8ad76811b2 # via -r src/backend/requirements.in -drf-spectacular==0.28.0 +drf-spectacular==0.28.0 \ + --hash=sha256:2c778a47a40ab2f5078a7c42e82baba07397bb35b074ae4680721b2805943061 \ + --hash=sha256:856e7edf1056e49a4245e87a61e8da4baff46c83dbc25be1da2df77f354c7cb4 # via -r src/backend/requirements.in -dulwich==0.22.7 +dulwich==0.22.7 \ + --hash=sha256:007d8160b511bb149d31c08548307982f6ce752a46e7088b020517de00c3bd46 \ + --hash=sha256:01544915c4056d0820de8cf126b971f7c180743ff64c4435c89168e44b30df4b \ + --hash=sha256:01e484d44014fef78cdef3b3adc34564808b4677497a57a0950c90a1d6349be3 \ + --hash=sha256:052715452b729544c611a107b2eef6111e527f041c1b666f8ed36c04e39c36b5 \ + --hash=sha256:10c5ee20430714ea6a79dde22c1f77078848930d27021aa810204738bc175e95 \ + --hash=sha256:1782854c10878b5cb8423e74b0ef4256c3667f7b0266513af028ac28dbab1f2d \ + --hash=sha256:1cbd5ecbc95e18c745965fc7b2b71209443987a99e499c7bb074234d7c6142e2 \ + --hash=sha256:2220c8b7cac5794e2260a924e81b05baa7836c18ba805d5a6731071a5ff6b860 \ + --hash=sha256:257abd49a768a52cf7f508daf2d30fe73f54fd32b7a674abd43817f66b0ca17b \ + --hash=sha256:2b7a3ac4baa49bd988cc0d0891a93aa26307c01f35caeed8729b7928a1f483af \ + --hash=sha256:40260034a6ecc3141a0d42360e888a73e58b9c0c9363c454cae182957fe602ac \ + --hash=sha256:5ada6a2fd400a4f51adfedd0267bfb08c61e2d9846c18ea653b0eb88a7b851d0 \ + --hash=sha256:5b9806a75f4b74fa891926b1d830e21f9cead80ed6dd803ed668369b26fb8b5f \ + --hash=sha256:62027dfccee97268eadf0c54df3d72ce30e4402cf5cf06c021e474b9a9eb3536 \ + --hash=sha256:637a9ac27512b8c04e6a29bf92e3f73386cd85dfe8609f523ffbc96e659bde4b \ + --hash=sha256:6bda2eca0847c30a9312a72f219af9e63feb7d2ca89f47fdaa240b0d0cdd6b84 \ + --hash=sha256:6bea11b98e854ff2abec390eeac752586b83921a22091dae65470ccbb003fc1b \ + --hash=sha256:6c830d63c691b5f979964a2d6b325930b7a53f14836598352690601cd205f04b \ + --hash=sha256:71b20bd6a25658e968e813eb69164332d3a2ab6029b51d3c6af8b64f2471847a \ + --hash=sha256:74b7cf6f0d46ac777be617dad7c1b992380004de74c0e0652bed174686249f34 \ + --hash=sha256:753eec461434f0ccbe0956ec825250e12230e8f1b365c8be1604386d94c2d8d0 \ + --hash=sha256:7649f0c9b4760d72768805155e66579761f282fdca123e351019c85efce811eb \ + --hash=sha256:7d72ce1377eac23bd77aa3541ceb91f2d8bd68687659f8625af8301f0b6b0a63 \ + --hash=sha256:8dd5df3919c648887e550e836f87b4b83f1429876adce5ead5b5977e333c874d \ + --hash=sha256:925cec97aeefda3f950e45e8d4c247e4ce6f83b6ee96e383c82f9bced626151f \ + --hash=sha256:986943e27a5c94c0be42fdcc688be1ae1a1349a3dbaa773fa7f9bdada1232b68 \ + --hash=sha256:9c01db2ef6d5f5b9192c0011624701b0de328868fe0c32601368cd337e77cd1a \ + --hash=sha256:9f418779837a3249b7dfc4b3dc7266fa40687e5f0249eedfa7185560ba1ee148 \ + --hash=sha256:9f5954cd491313743d7bd3623d323b72afceb83d2c2a47921f621bdd9d4c615b \ + --hash=sha256:a64e61fa6ab60db0f897f1c30f32b26b330d3a9dc264f089ee9c44f5900fb657 \ + --hash=sha256:a8886b2c9750ba15193356d9e8608e031cd89a780d0afc53b3101391605b3793 \ + --hash=sha256:aa0bb9afa799c0301b2760e9af99083a2b08f655c55037945b6a5e227566adc1 \ + --hash=sha256:b25848041c51d09affafd2708236205cc4483bed8f7f43ecbe63b6a66b447604 \ + --hash=sha256:bb258c62d7fb4cfe03b3fba09f702ebb84a924f2f004833435e32c93fe8a7f13 \ + --hash=sha256:c68ab3540809bedcdd9b99e51c12adf11c2ab26554f74d899d8cf55bfa2639a6 \ + --hash=sha256:ca7ed207956001e6a8a2e3f319cdc37591e53f7eb04aedafa78f96768048c53e \ + --hash=sha256:cdbcf206d4b1e5ba2affc6189948cb292cc647593876b96a0b71db44e79a05a1 \ + --hash=sha256:d53935832dd182d4c1415042187093efcee988af5cd397fb1f394f5bb27f0707 \ + --hash=sha256:df5a179e5d95ac0263b5e0ccd53311eac486091979dcac106c5cc9e0ee4f2aa2 \ + --hash=sha256:f73668ecc29e0a20d20970489fffe2ba466e5486eae2f20104bc38bcbe611f64 \ + --hash=sha256:fdbd087e9e99bc809b15864ebc79dbefe869e3038b64c953d7736f6e6b382dc7 \ + --hash=sha256:fe324dc40b93e8be996c9fa9291a439bef835a92a2e4cb5c8cbdb1171c168fd6 # via -r src/backend/requirements.in -et-xmlfile==2.0.0 +et-xmlfile==2.0.0 \ + --hash=sha256:7a91720bc756843502c3b7504c77b8fe44217c85c537d85037f0f536151b2caa \ + --hash=sha256:dab3f4764309081ce75662649be815c4c9081e88f0837825f90fd28317d4da54 # via openpyxl -feedparser==6.0.11 +feedparser==6.0.11 \ + --hash=sha256:0be7ee7b395572b19ebeb1d6aafb0028dee11169f1c934e0ed67d54992f4ad45 \ + --hash=sha256:c9d0407b64c6f2a065d0ebb292c2b35c01050cc0dc33757461aaabdc4c4184d5 # via -r src/backend/requirements.in -fido2==1.2.0 +fido2==1.2.0 \ + --hash=sha256:e39f95920122d64283fda5e5581d95a206e704fa42846bfa4662f86aa0d3333b \ + --hash=sha256:f7c8ee62e359aa980a45773f9493965bb29ede1b237a9218169dbfe60c80e130 # via django-allauth -flexcache==0.3 +flexcache==0.3 \ + --hash=sha256:18743bd5a0621bfe2cf8d519e4c3bfdf57a269c15d1ced3fb4b64e0ff4600656 \ + --hash=sha256:d43c9fea82336af6e0115e308d9d33a185390b8346a017564611f1466dcd2e32 # via pint -flexparser==0.4 +flexparser==0.4 \ + --hash=sha256:266d98905595be2ccc5da964fe0a2c3526fbbffdc45b65b3146d75db992ef6b2 \ + --hash=sha256:3738b456192dcb3e15620f324c447721023c0293f6af9955b481e91d00179846 # via pint -fonttools[woff]==4.55.8 +fonttools[woff]==4.55.3 \ + --hash=sha256:07f8288aacf0a38d174445fc78377a97fb0b83cfe352a90c9d9c1400571963c7 \ + --hash=sha256:11e5de1ee0d95af4ae23c1a138b184b7f06e0b6abacabf1d0db41c90b03d834b \ + --hash=sha256:1bc7ad24ff98846282eef1cbeac05d013c2154f977a79886bb943015d2b1b261 \ + --hash=sha256:1dcc07934a2165ccdc3a5a608db56fb3c24b609658a5b340aee4ecf3ba679dc0 \ + --hash=sha256:22f38464daa6cdb7b6aebd14ab06609328fe1e9705bb0fcc7d1e69de7109ee02 \ + --hash=sha256:27e4ae3592e62eba83cd2c4ccd9462dcfa603ff78e09110680a5444c6925d841 \ + --hash=sha256:3983313c2a04d6cc1fe9251f8fc647754cf49a61dac6cb1e7249ae67afaafc45 \ + --hash=sha256:529cef2ce91dc44f8e407cc567fae6e49a1786f2fefefa73a294704c415322a4 \ + --hash=sha256:5323a22eabddf4b24f66d26894f1229261021dacd9d29e89f7872dd8c63f0b8b \ + --hash=sha256:54153c49913f45065c8d9e6d0c101396725c5621c8aee744719300f79771d75a \ + --hash=sha256:546565028e244a701f73df6d8dd6be489d01617863ec0c6a42fa25bf45d43048 \ + --hash=sha256:5480673f599ad410695ca2ddef2dfefe9df779a9a5cda89503881e503c9c7d90 \ + --hash=sha256:5e8d657cd7326eeaba27de2740e847c6b39dde2f8d7cd7cc56f6aad404ddf0bd \ + --hash=sha256:62d65a3022c35e404d19ca14f291c89cc5890032ff04f6c17af0bd1927299674 \ + --hash=sha256:6314bf82c54c53c71805318fcf6786d986461622dd926d92a465199ff54b1b72 \ + --hash=sha256:7a8aa2c5e5b8b3bcb2e4538d929f6589a5c6bdb84fd16e2ed92649fb5454f11c \ + --hash=sha256:827e95fdbbd3e51f8b459af5ea10ecb4e30af50221ca103bea68218e9615de07 \ + --hash=sha256:859c358ebf41db18fb72342d3080bce67c02b39e86b9fbcf1610cca14984841b \ + --hash=sha256:86721fbc389ef5cc1e2f477019e5069e8e4421e8d9576e9c26f840dbb04678de \ + --hash=sha256:89bdc5d88bdeec1b15af790810e267e8332d92561dce4f0748c2b95c9bdf3926 \ + --hash=sha256:8c4491699bad88efe95772543cd49870cf756b019ad56294f6498982408ab03e \ + --hash=sha256:8c5ec45428edaa7022f1c949a632a6f298edc7b481312fc7dc258921e9399628 \ + --hash=sha256:8e75f12c82127486fac2d8bfbf5bf058202f54bf4f158d367e41647b972342ca \ + --hash=sha256:a430178ad3e650e695167cb53242dae3477b35c95bef6525b074d87493c4bf29 \ + --hash=sha256:a8c2794ded89399cc2169c4d0bf7941247b8d5932b2659e09834adfbb01589aa \ + --hash=sha256:aca318b77f23523309eec4475d1fbbb00a6b133eb766a8bdc401faba91261abe \ + --hash=sha256:ae3b6600565b2d80b7c05acb8e24d2b26ac407b27a3f2e078229721ba5698427 \ + --hash=sha256:aedbeb1db64496d098e6be92b2e63b5fac4e53b1b92032dfc6988e1ea9134a4d \ + --hash=sha256:aee3b57643827e237ff6ec6d28d9ff9766bd8b21e08cd13bff479e13d4b14765 \ + --hash=sha256:b54baf65c52952db65df39fcd4820668d0ef4766c0ccdf32879b77f7c804d5c5 \ + --hash=sha256:b586ab5b15b6097f2fb71cafa3c98edfd0dba1ad8027229e7b1e204a58b0e09d \ + --hash=sha256:b8d5e8916c0970fbc0f6f1bece0063363bb5857a7f170121a4493e31c3db3314 \ + --hash=sha256:bc5dbb4685e51235ef487e4bd501ddfc49be5aede5e40f4cefcccabc6e60fb4b \ + --hash=sha256:bdcc9f04b36c6c20978d3f060e5323a43f6222accc4e7fcbef3f428e216d96af \ + --hash=sha256:c3ca99e0d460eff46e033cd3992a969658c3169ffcd533e0a39c63a38beb6831 \ + --hash=sha256:caf8230f3e10f8f5d7593eb6d252a37caf58c480b19a17e250a63dad63834cf3 \ + --hash=sha256:cd70de1a52a8ee2d1877b6293af8a2484ac82514f10b1c67c1c5762d38073e56 \ + --hash=sha256:cf4fe7c124aa3f4e4c1940880156e13f2f4d98170d35c749e6b4f119a872551e \ + --hash=sha256:d342e88764fb201286d185093781bf6628bbe380a913c24adf772d901baa8276 \ + --hash=sha256:da9da6d65cd7aa6b0f806556f4985bcbf603bf0c5c590e61b43aa3e5a0f822d0 \ + --hash=sha256:dc5294a3d5c84226e3dbba1b6f61d7ad813a8c0238fceea4e09aa04848c3d851 \ + --hash=sha256:dd68c87a2bfe37c5b33bcda0fba39b65a353876d3b9006fde3adae31f97b3ef5 \ + --hash=sha256:e6e8766eeeb2de759e862004aa11a9ea3d6f6d5ec710551a88b476192b64fd54 \ + --hash=sha256:e894b5bd60d9f473bed7a8f506515549cc194de08064d829464088d23097331b \ + --hash=sha256:eb6ca911c4c17eb51853143624d8dc87cdcdf12a711fc38bf5bd21521e79715f \ + --hash=sha256:ed63959d00b61959b035c7d47f9313c2c1ece090ff63afea702fe86de00dbed4 \ + --hash=sha256:f412604ccbeee81b091b420272841e5ec5ef68967a9790e80bffd0e30b8e2977 \ + --hash=sha256:f7d66c15ba875432a2d2fb419523f5d3d347f91f48f57b8b08a2dfc3c39b8a3f \ + --hash=sha256:f9e736f60f4911061235603a6119e72053073a12c6d7904011df2d8fad2c0e35 \ + --hash=sha256:fb594b5a99943042c702c550d5494bdd7577f6ef19b0bc73877c948a63184a32 # via weasyprint -googleapis-common-protos==1.66.0 +googleapis-common-protos==1.66.0 \ + --hash=sha256:c3e7b33d15fdca5374cc0a7346dd92ffa847425cc4ea941d970f13680052ec8c \ + --hash=sha256:d7abcd75fabb2e0ec9f74466401f6c119a0b498e27370e9be4c94cb7e382b8ed # via # opentelemetry-exporter-otlp-proto-grpc # opentelemetry-exporter-otlp-proto-http -grpcio==1.70.0 +grpcio==1.69.0 \ + --hash=sha256:01f834732c22a130bdf3dc154d1053bdbc887eb3ccb7f3e6285cfbfc33d9d5cc \ + --hash=sha256:028337786f11fecb5d7b7fa660475a06aabf7e5e52b5ac2df47414878c0ce7ea \ + --hash=sha256:0470fa911c503af59ec8bc4c82b371ee4303ececbbdc055f55ce48e38b20fd67 \ + --hash=sha256:0f0270bd9ffbff6961fe1da487bdcd594407ad390cc7960e738725d4807b18c4 \ + --hash=sha256:1227ff7836f7b3a4ab04e5754f1d001fa52a730685d3dc894ed8bc262cc96c01 \ + --hash=sha256:1514341def9c6ec4b7f0b9628be95f620f9d4b99331b7ef0a1845fd33d9b579c \ + --hash=sha256:1e925954b18d41aeb5ae250262116d0970893b38232689c4240024e4333ac084 \ + --hash=sha256:1ee76cd7e2e49cf9264f6812d8c9ac1b85dda0eaea063af07292400f9191750e \ + --hash=sha256:1f03dc9b4da4c0dc8a1db7a5420f575251d7319b7a839004d8916257ddbe4816 \ + --hash=sha256:200e48a6e7b00f804cf00a1c26292a5baa96507c7749e70a3ec10ca1a288936e \ + --hash=sha256:2060ca95a8db295ae828d0fc1c7f38fb26ccd5edf9aa51a0f44251f5da332e97 \ + --hash=sha256:26c9a9c4ac917efab4704b18eed9082ed3b6ad19595f047e8173b5182fec0d5e \ + --hash=sha256:282f47d0928e40f25d007f24eb8fa051cb22551e3c74b8248bc9f9bea9c35fe0 \ + --hash=sha256:2e52e107261fd8fa8fa457fe44bfadb904ae869d87c1280bf60f93ecd3e79278 \ + --hash=sha256:316463c0832d5fcdb5e35ff2826d9aa3f26758d29cdfb59a368c1d6c39615a11 \ + --hash=sha256:3629d8a8185f5139869a6a17865d03113a260e311e78fbe313f1a71603617589 \ + --hash=sha256:3b75aea7c6cb91b341c85e7c1d9db1e09e1dd630b0717f836be94971e015031e \ + --hash=sha256:45a4704339b6e5b24b0e136dea9ad3815a94f30eb4f1e1d44c4ac484ef11d8dd \ + --hash=sha256:4ed866f9edb574fd9be71bf64c954ce1b88fc93b2a4cbf94af221e9426eb14d6 \ + --hash=sha256:5494d0e52bf77a2f7eb17c6da662886ca0a731e56c1c85b93505bece8dc6cf4c \ + --hash=sha256:5ccbed100dc43704e94ccff9e07680b540d64e4cc89213ab2832b51b4f68a520 \ + --hash=sha256:5cfd14175f9db33d4b74d63de87c64bb0ee29ce475ce3c00c01ad2a3dc2a9e51 \ + --hash=sha256:60e5de105dc02832dc8f120056306d0ef80932bcf1c0e2b4ca3b676de6dc6505 \ + --hash=sha256:7e76accf38808f5c5c752b0ab3fd919eb14ff8fafb8db520ad1cc12afff74de6 \ + --hash=sha256:85d347cb8237751b23539981dbd2d9d8f6e9ff90082b427b13022b948eb6347a \ + --hash=sha256:87d222569273720366f68a99cb62e6194681eb763ee1d3b1005840678d4884f9 \ + --hash=sha256:8b94e83f66dbf6fd642415faca0608590bc5e8d30e2c012b31d7d1b91b1de2fd \ + --hash=sha256:8cc614e895177ab7e4b70f154d1a7c97e152577ea101d76026d132b7aaba003b \ + --hash=sha256:8de1b192c29b8ce45ee26a700044717bcbbd21c697fa1124d440548964328561 \ + --hash=sha256:9031069d36cb949205293cf0e243abd5e64d6c93e01b078c37921493a41b72dc \ + --hash=sha256:90b3646ced2eae3a0599658eeccc5ba7f303bf51b82514c50715bdd2b109e5ec \ + --hash=sha256:936fa44241b5379c5afc344e1260d467bee495747eaf478de825bab2791da6f5 \ + --hash=sha256:a78a06911d4081a24a1761d16215a08e9b6d4d29cdbb7e427e6c7e17b06bcc5d \ + --hash=sha256:a7f4ed0dcf202a70fe661329f8874bc3775c14bb3911d020d07c82c766ce0eb1 \ + --hash=sha256:b192b81076073ed46f4b4dd612b8897d9a1e39d4eabd822e5da7b38497ed77e1 \ + --hash=sha256:b62b0f41e6e01a3e5082000b612064c87c93a49b05f7602fe1b7aa9fd5171a1d \ + --hash=sha256:b634851b92c090763dde61df0868c730376cdb73a91bcc821af56ae043b09596 \ + --hash=sha256:b650f34aceac8b2d08a4c8d7dc3e8a593f4d9e26d86751ebf74ebf5107d927de \ + --hash=sha256:b7f693db593d6bf285e015d5538bf1c86cf9c60ed30b6f7da04a00ed052fe2f3 \ + --hash=sha256:bf1f8be0da3fcdb2c1e9f374f3c2d043d606d69f425cd685110dd6d0d2d61258 \ + --hash=sha256:bf5f680d3ed08c15330d7830d06bc65f58ca40c9999309517fd62880d70cb06e \ + --hash=sha256:c1fea55d26d647346acb0069b08dca70984101f2dc95066e003019207212e303 \ + --hash=sha256:c5ba38aeac7a2fe353615c6b4213d1fbb3a3c34f86b4aaa8be08baaaee8cc56d \ + --hash=sha256:c9a281878feeb9ae26db0622a19add03922a028d4db684658f16d546601a4870 \ + --hash=sha256:ca71d73a270dff052fe4edf74fef142d6ddd1f84175d9ac4a14b7280572ac519 \ + --hash=sha256:cc89b6c29f3dccbe12d7a3b3f1b3999db4882ae076c1c1f6df231d55dbd767a5 \ + --hash=sha256:cd7ea241b10bc5f0bb0f82c0d7896822b7ed122b3ab35c9851b440c1ccf81588 \ + --hash=sha256:d5658c3c2660417d82db51e168b277e0ff036d0b0f859fa7576c0ffd2aec1442 \ + --hash=sha256:db6f9fd2578dbe37db4b2994c94a1d9c93552ed77dca80e1657bb8a05b898b55 \ + --hash=sha256:dc48f99cc05e0698e689b51a05933253c69a8c8559a47f605cff83801b03af0e \ + --hash=sha256:dc5a351927d605b2721cbb46158e431dd49ce66ffbacb03e709dc07a491dde35 \ + --hash=sha256:dd034d68a2905464c49479b0c209c773737a4245d616234c79c975c7c90eca03 \ + --hash=sha256:f79e05f5bbf551c4057c227d1b041ace0e78462ac8128e2ad39ec58a382536d2 \ + --hash=sha256:fb9302afc3a0e4ba0b225cd651ef8e478bf0070cf11a529175caecd5ea2474e7 \ + --hash=sha256:fc18a4de8c33491ad6f70022af5c460b39611e39578a4d84de0fe92f12d5d47b # via # -r src/backend/requirements.in # opentelemetry-exporter-otlp-proto-grpc -gunicorn==23.0.0 +gunicorn==23.0.0 \ + --hash=sha256:ec400d38950de4dfd418cff8328b2c8faed0edb0d517d3394e457c317908ca4d \ + --hash=sha256:f014447a0101dc57e294f6c18ca6b40227a4c90e9bdb586042628030cba004ec # via -r src/backend/requirements.in -html5lib==1.1 +html5lib==1.1 \ + --hash=sha256:0d78f8fde1c230e99fe37986a60526d7049ed4bf8a9fadbad5f00e22e58e041d \ + --hash=sha256:b2e5b40261e20f354d198eae92afc10d750afb487ed5e50f9c4eaf07c184146f # via weasyprint -icalendar==6.1.1 +icalendar==6.1.0 \ + --hash=sha256:43c2db8632959d634f4e48f6e6131e706bf2cdddad488cf0b72fda079b796bad \ + --hash=sha256:46c09b774a6e6948495dafcb166dc15135c8259d0ae25491f154cbc822714b69 # via django-ical -idna==3.10 +idna==3.10 \ + --hash=sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9 \ + --hash=sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3 # via requests -importlib-metadata==8.5.0 +importlib-metadata==8.5.0 \ + --hash=sha256:45e54197d28b7a7f1559e60b95e7c567032b602131fbd588f1497f47880aa68b \ + --hash=sha256:71522656f0abace1d072b9e5481a48f07c138e00f079c38c8f883823f9c26bd7 # via # django-q2 # markdown # opentelemetry-api -inflection==0.5.1 +inflection==0.5.1 \ + --hash=sha256:1a29730d366e996aaacffb2f1f1cb9593dc38e2ddd30c91250c6dde09ea9b417 \ + --hash=sha256:f38b2b640938a4f35ade69ac3d053042959b62a0f1076a5bbaa1b9526605a8a2 # via drf-spectacular -isodate==0.7.2 +isodate==0.7.2 \ + --hash=sha256:28009937d8031054830160fce6d409ed342816b543597cece116d966c6d99e15 \ + --hash=sha256:4cd1aa0f43ca76f4a6c6c0292a85f40b35ec2e43e315b59f06e6d32171a953e6 # via python3-saml -itypes==1.2.0 +itypes==1.2.0 \ + --hash=sha256:03da6872ca89d29aef62773672b2d408f490f80db48b23079a4b194c86dd04c6 \ + --hash=sha256:af886f129dea4a2a1e3d36595a2d139589e4dd287f5cab0b40e799ee81570ff1 # via coreapi -jinja2==3.1.5 +jinja2==3.1.5 \ + --hash=sha256:8fefff8dc3034e27bb80d67c671eb8a9bc424c0ef4c0826edbff304cceff43bb \ + --hash=sha256:aba0f4dc9ed8013c424088f68a5c226f7d6097ed89b246d7749c2ec4175c6adb # via coreschema -jsonschema==4.23.0 +jsonschema==4.23.0 \ + --hash=sha256:d71497fef26351a33265337fa77ffeb82423f3ea21283cd9467bb03999266bc4 \ + --hash=sha256:fbadb6f8b144a8f8cf9f0b89ba94501d143e50411a1278633f56a7acf7fd5566 # via drf-spectacular -jsonschema-specifications==2024.10.1 +jsonschema-specifications==2024.10.1 \ + --hash=sha256:0f38b83639958ce1152d02a7f062902c41c8fd20d558b0c34344292d417ae272 \ + --hash=sha256:a09a0680616357d9a0ecf05c12ad234479f549239d0f5b55f3deea67475da9bf # via jsonschema -lxml==5.3.0 +lxml==5.3.0 \ + --hash=sha256:01220dca0d066d1349bd6a1726856a78f7929f3878f7e2ee83c296c69495309e \ + --hash=sha256:02ced472497b8362c8e902ade23e3300479f4f43e45f4105c85ef43b8db85229 \ + --hash=sha256:052d99051e77a4f3e8482c65014cf6372e61b0a6f4fe9edb98503bb5364cfee3 \ + --hash=sha256:07da23d7ee08577760f0a71d67a861019103e4812c87e2fab26b039054594cc5 \ + --hash=sha256:094cb601ba9f55296774c2d57ad68730daa0b13dc260e1f941b4d13678239e70 \ + --hash=sha256:0a7056921edbdd7560746f4221dca89bb7a3fe457d3d74267995253f46343f15 \ + --hash=sha256:0c120f43553ec759f8de1fee2f4794452b0946773299d44c36bfe18e83caf002 \ + --hash=sha256:0d7b36afa46c97875303a94e8f3ad932bf78bace9e18e603f2085b652422edcd \ + --hash=sha256:0fdf3a3059611f7585a78ee10399a15566356116a4288380921a4b598d807a22 \ + --hash=sha256:109fa6fede314cc50eed29e6e56c540075e63d922455346f11e4d7a036d2b8cf \ + --hash=sha256:146173654d79eb1fc97498b4280c1d3e1e5d58c398fa530905c9ea50ea849b22 \ + --hash=sha256:1473427aff3d66a3fa2199004c3e601e6c4500ab86696edffdbc84954c72d832 \ + --hash=sha256:1483fd3358963cc5c1c9b122c80606a3a79ee0875bcac0204149fa09d6ff2727 \ + --hash=sha256:168f2dfcfdedf611eb285efac1516c8454c8c99caf271dccda8943576b67552e \ + --hash=sha256:17e8d968d04a37c50ad9c456a286b525d78c4a1c15dd53aa46c1d8e06bf6fa30 \ + --hash=sha256:18feb4b93302091b1541221196a2155aa296c363fd233814fa11e181adebc52f \ + --hash=sha256:1afe0a8c353746e610bd9031a630a95bcfb1a720684c3f2b36c4710a0a96528f \ + --hash=sha256:1d04f064bebdfef9240478f7a779e8c5dc32b8b7b0b2fc6a62e39b928d428e51 \ + --hash=sha256:1fdc9fae8dd4c763e8a31e7630afef517eab9f5d5d31a278df087f307bf601f4 \ + --hash=sha256:1ffc23010330c2ab67fac02781df60998ca8fe759e8efde6f8b756a20599c5de \ + --hash=sha256:20094fc3f21ea0a8669dc4c61ed7fa8263bd37d97d93b90f28fc613371e7a875 \ + --hash=sha256:213261f168c5e1d9b7535a67e68b1f59f92398dd17a56d934550837143f79c42 \ + --hash=sha256:218c1b2e17a710e363855594230f44060e2025b05c80d1f0661258142b2add2e \ + --hash=sha256:23e0553b8055600b3bf4a00b255ec5c92e1e4aebf8c2c09334f8368e8bd174d6 \ + --hash=sha256:25f1b69d41656b05885aa185f5fdf822cb01a586d1b32739633679699f220391 \ + --hash=sha256:2b3778cb38212f52fac9fe913017deea2fdf4eb1a4f8e4cfc6b009a13a6d3fcc \ + --hash=sha256:2bc9fd5ca4729af796f9f59cd8ff160fe06a474da40aca03fcc79655ddee1a8b \ + --hash=sha256:2c226a06ecb8cdef28845ae976da407917542c5e6e75dcac7cc33eb04aaeb237 \ + --hash=sha256:2c3406b63232fc7e9b8783ab0b765d7c59e7c59ff96759d8ef9632fca27c7ee4 \ + --hash=sha256:2c86bf781b12ba417f64f3422cfc302523ac9cd1d8ae8c0f92a1c66e56ef2e86 \ + --hash=sha256:2d9b8d9177afaef80c53c0a9e30fa252ff3036fb1c6494d427c066a4ce6a282f \ + --hash=sha256:2dec2d1130a9cda5b904696cec33b2cfb451304ba9081eeda7f90f724097300a \ + --hash=sha256:2dfab5fa6a28a0b60a20638dc48e6343c02ea9933e3279ccb132f555a62323d8 \ + --hash=sha256:2ecdd78ab768f844c7a1d4a03595038c166b609f6395e25af9b0f3f26ae1230f \ + --hash=sha256:315f9542011b2c4e1d280e4a20ddcca1761993dda3afc7a73b01235f8641e903 \ + --hash=sha256:36aef61a1678cb778097b4a6eeae96a69875d51d1e8f4d4b491ab3cfb54b5a03 \ + --hash=sha256:384aacddf2e5813a36495233b64cb96b1949da72bef933918ba5c84e06af8f0e \ + --hash=sha256:3879cc6ce938ff4eb4900d901ed63555c778731a96365e53fadb36437a131a99 \ + --hash=sha256:3c174dc350d3ec52deb77f2faf05c439331d6ed5e702fc247ccb4e6b62d884b7 \ + --hash=sha256:3eb44520c4724c2e1a57c0af33a379eee41792595023f367ba3952a2d96c2aab \ + --hash=sha256:406246b96d552e0503e17a1006fd27edac678b3fcc9f1be71a2f94b4ff61528d \ + --hash=sha256:41ce1f1e2c7755abfc7e759dc34d7d05fd221723ff822947132dc934d122fe22 \ + --hash=sha256:423b121f7e6fa514ba0c7918e56955a1d4470ed35faa03e3d9f0e3baa4c7e492 \ + --hash=sha256:44264ecae91b30e5633013fb66f6ddd05c006d3e0e884f75ce0b4755b3e3847b \ + --hash=sha256:482c2f67761868f0108b1743098640fbb2a28a8e15bf3f47ada9fa59d9fe08c3 \ + --hash=sha256:4b0c7a688944891086ba192e21c5229dea54382f4836a209ff8d0a660fac06be \ + --hash=sha256:4c1fefd7e3d00921c44dc9ca80a775af49698bbfd92ea84498e56acffd4c5469 \ + --hash=sha256:4e109ca30d1edec1ac60cdbe341905dc3b8f55b16855e03a54aaf59e51ec8c6f \ + --hash=sha256:501d0d7e26b4d261fca8132854d845e4988097611ba2531408ec91cf3fd9d20a \ + --hash=sha256:516f491c834eb320d6c843156440fe7fc0d50b33e44387fcec5b02f0bc118a4c \ + --hash=sha256:51806cfe0279e06ed8500ce19479d757db42a30fd509940b1701be9c86a5ff9a \ + --hash=sha256:562e7494778a69086f0312ec9689f6b6ac1c6b65670ed7d0267e49f57ffa08c4 \ + --hash=sha256:56b9861a71575f5795bde89256e7467ece3d339c9b43141dbdd54544566b3b94 \ + --hash=sha256:5b8f5db71b28b8c404956ddf79575ea77aa8b1538e8b2ef9ec877945b3f46442 \ + --hash=sha256:5c2fb570d7823c2bbaf8b419ba6e5662137f8166e364a8b2b91051a1fb40ab8b \ + --hash=sha256:5c54afdcbb0182d06836cc3d1be921e540be3ebdf8b8a51ee3ef987537455f84 \ + --hash=sha256:5d6a6972b93c426ace71e0be9a6f4b2cfae9b1baed2eed2006076a746692288c \ + --hash=sha256:609251a0ca4770e5a8768ff902aa02bf636339c5a93f9349b48eb1f606f7f3e9 \ + --hash=sha256:62d172f358f33a26d6b41b28c170c63886742f5b6772a42b59b4f0fa10526cb1 \ + --hash=sha256:62f7fdb0d1ed2065451f086519865b4c90aa19aed51081979ecd05a21eb4d1be \ + --hash=sha256:658f2aa69d31e09699705949b5fc4719cbecbd4a97f9656a232e7d6c7be1a367 \ + --hash=sha256:65ab5685d56914b9a2a34d67dd5488b83213d680b0c5d10b47f81da5a16b0b0e \ + --hash=sha256:68934b242c51eb02907c5b81d138cb977b2129a0a75a8f8b60b01cb8586c7b21 \ + --hash=sha256:68b87753c784d6acb8a25b05cb526c3406913c9d988d51f80adecc2b0775d6aa \ + --hash=sha256:69959bd3167b993e6e710b99051265654133a98f20cec1d9b493b931942e9c16 \ + --hash=sha256:6a7095eeec6f89111d03dabfe5883a1fd54da319c94e0fb104ee8f23616b572d \ + --hash=sha256:6b038cc86b285e4f9fea2ba5ee76e89f21ed1ea898e287dc277a25884f3a7dfe \ + --hash=sha256:6ba0d3dcac281aad8a0e5b14c7ed6f9fa89c8612b47939fc94f80b16e2e9bc83 \ + --hash=sha256:6e91cf736959057f7aac7adfc83481e03615a8e8dd5758aa1d95ea69e8931dba \ + --hash=sha256:6ee8c39582d2652dcd516d1b879451500f8db3fe3607ce45d7c5957ab2596040 \ + --hash=sha256:6f651ebd0b21ec65dfca93aa629610a0dbc13dbc13554f19b0113da2e61a4763 \ + --hash=sha256:71a8dd38fbd2f2319136d4ae855a7078c69c9a38ae06e0c17c73fd70fc6caad8 \ + --hash=sha256:74068c601baff6ff021c70f0935b0c7bc528baa8ea210c202e03757c68c5a4ff \ + --hash=sha256:7437237c6a66b7ca341e868cda48be24b8701862757426852c9b3186de1da8a2 \ + --hash=sha256:747a3d3e98e24597981ca0be0fd922aebd471fa99d0043a3842d00cdcad7ad6a \ + --hash=sha256:74bcb423462233bc5d6066e4e98b0264e7c1bed7541fff2f4e34fe6b21563c8b \ + --hash=sha256:78d9b952e07aed35fe2e1a7ad26e929595412db48535921c5013edc8aa4a35ce \ + --hash=sha256:7b1cd427cb0d5f7393c31b7496419da594fe600e6fdc4b105a54f82405e6626c \ + --hash=sha256:7d3d1ca42870cdb6d0d29939630dbe48fa511c203724820fc0fd507b2fb46577 \ + --hash=sha256:7e2f58095acc211eb9d8b5771bf04df9ff37d6b87618d1cbf85f92399c98dae8 \ + --hash=sha256:7f41026c1d64043a36fda21d64c5026762d53a77043e73e94b71f0521939cc71 \ + --hash=sha256:81b4e48da4c69313192d8c8d4311e5d818b8be1afe68ee20f6385d0e96fc9512 \ + --hash=sha256:86a6b24b19eaebc448dc56b87c4865527855145d851f9fc3891673ff97950540 \ + --hash=sha256:874a216bf6afaf97c263b56371434e47e2c652d215788396f60477540298218f \ + --hash=sha256:89e043f1d9d341c52bf2af6d02e6adde62e0a46e6755d5eb60dc6e4f0b8aeca2 \ + --hash=sha256:8c72e9563347c7395910de6a3100a4840a75a6f60e05af5e58566868d5eb2d6a \ + --hash=sha256:8dc2c0395bea8254d8daebc76dcf8eb3a95ec2a46fa6fae5eaccee366bfe02ce \ + --hash=sha256:8f0de2d390af441fe8b2c12626d103540b5d850d585b18fcada58d972b74a74e \ + --hash=sha256:92e67a0be1639c251d21e35fe74df6bcc40cba445c2cda7c4a967656733249e2 \ + --hash=sha256:94d6c3782907b5e40e21cadf94b13b0842ac421192f26b84c45f13f3c9d5dc27 \ + --hash=sha256:97acf1e1fd66ab53dacd2c35b319d7e548380c2e9e8c54525c6e76d21b1ae3b1 \ + --hash=sha256:9ada35dd21dc6c039259596b358caab6b13f4db4d4a7f8665764d616daf9cc1d \ + --hash=sha256:9c52100e2c2dbb0649b90467935c4b0de5528833c76a35ea1a2691ec9f1ee7a1 \ + --hash=sha256:9e41506fec7a7f9405b14aa2d5c8abbb4dbbd09d88f9496958b6d00cb4d45330 \ + --hash=sha256:9e4b47ac0f5e749cfc618efdf4726269441014ae1d5583e047b452a32e221920 \ + --hash=sha256:9fb81d2824dff4f2e297a276297e9031f46d2682cafc484f49de182aa5e5df99 \ + --hash=sha256:a0eabd0a81625049c5df745209dc7fcef6e2aea7793e5f003ba363610aa0a3ff \ + --hash=sha256:a3d819eb6f9b8677f57f9664265d0a10dd6551d227afb4af2b9cd7bdc2ccbf18 \ + --hash=sha256:a87de7dd873bf9a792bf1e58b1c3887b9264036629a5bf2d2e6579fe8e73edff \ + --hash=sha256:aa617107a410245b8660028a7483b68e7914304a6d4882b5ff3d2d3eb5948d8c \ + --hash=sha256:aac0bbd3e8dd2d9c45ceb82249e8bdd3ac99131a32b4d35c8af3cc9db1657179 \ + --hash=sha256:ab6dd83b970dc97c2d10bc71aa925b84788c7c05de30241b9e96f9b6d9ea3080 \ + --hash=sha256:ace2c2326a319a0bb8a8b0e5b570c764962e95818de9f259ce814ee666603f19 \ + --hash=sha256:ae5fe5c4b525aa82b8076c1a59d642c17b6e8739ecf852522c6321852178119d \ + --hash=sha256:b11a5d918a6216e521c715b02749240fb07ae5a1fefd4b7bf12f833bc8b4fe70 \ + --hash=sha256:b1c8c20847b9f34e98080da785bb2336ea982e7f913eed5809e5a3c872900f32 \ + --hash=sha256:b369d3db3c22ed14c75ccd5af429086f166a19627e84a8fdade3f8f31426e52a \ + --hash=sha256:b710bc2b8292966b23a6a0121f7a6c51d45d2347edcc75f016ac123b8054d3f2 \ + --hash=sha256:bd96517ef76c8654446fc3db9242d019a1bb5fe8b751ba414765d59f99210b79 \ + --hash=sha256:c00f323cc00576df6165cc9d21a4c21285fa6b9989c5c39830c3903dc4303ef3 \ + --hash=sha256:c162b216070f280fa7da844531169be0baf9ccb17263cf5a8bf876fcd3117fa5 \ + --hash=sha256:c1a69e58a6bb2de65902051d57fde951febad631a20a64572677a1052690482f \ + --hash=sha256:c1f794c02903c2824fccce5b20c339a1a14b114e83b306ff11b597c5f71a1c8d \ + --hash=sha256:c24037349665434f375645fa9d1f5304800cec574d0310f618490c871fd902b3 \ + --hash=sha256:c300306673aa0f3ed5ed9372b21867690a17dba38c68c44b287437c362ce486b \ + --hash=sha256:c56a1d43b2f9ee4786e4658c7903f05da35b923fb53c11025712562d5cc02753 \ + --hash=sha256:c6379f35350b655fd817cd0d6cbeef7f265f3ae5fedb1caae2eb442bbeae9ab9 \ + --hash=sha256:c802e1c2ed9f0c06a65bc4ed0189d000ada8049312cfeab6ca635e39c9608957 \ + --hash=sha256:cb83f8a875b3d9b458cada4f880fa498646874ba4011dc974e071a0a84a1b033 \ + --hash=sha256:cf120cce539453ae086eacc0130a324e7026113510efa83ab42ef3fcfccac7fb \ + --hash=sha256:dd36439be765e2dde7660212b5275641edbc813e7b24668831a5c8ac91180656 \ + --hash=sha256:dd5350b55f9fecddc51385463a4f67a5da829bc741e38cf689f38ec9023f54ab \ + --hash=sha256:df5c7333167b9674aa8ae1d4008fa4bc17a313cc490b2cca27838bbdcc6bb15b \ + --hash=sha256:e63601ad5cd8f860aa99d109889b5ac34de571c7ee902d6812d5d9ddcc77fa7d \ + --hash=sha256:e92ce66cd919d18d14b3856906a61d3f6b6a8500e0794142338da644260595cd \ + --hash=sha256:e99f5507401436fdcc85036a2e7dc2e28d962550afe1cbfc07c40e454256a859 \ + --hash=sha256:ea2e2f6f801696ad7de8aec061044d6c8c0dd4037608c7cab38a9a4d316bfb11 \ + --hash=sha256:eafa2c8658f4e560b098fe9fc54539f86528651f61849b22111a9b107d18910c \ + --hash=sha256:ecd4ad8453ac17bc7ba3868371bffb46f628161ad0eefbd0a855d2c8c32dd81a \ + --hash=sha256:ee70d08fd60c9565ba8190f41a46a54096afa0eeb8f76bd66f2c25d3b1b83005 \ + --hash=sha256:eec1bb8cdbba2925bedc887bc0609a80e599c75b12d87ae42ac23fd199445654 \ + --hash=sha256:ef0c1fe22171dd7c7c27147f2e9c3e86f8bdf473fed75f16b0c2e84a5030ce80 \ + --hash=sha256:f2901429da1e645ce548bf9171784c0f74f0718c3f6150ce166be39e4dd66c3e \ + --hash=sha256:f422a209d2455c56849442ae42f25dbaaba1c6c3f501d58761c619c7836642ec \ + --hash=sha256:f65e5120863c2b266dbcc927b306c5b78e502c71edf3295dfcb9501ec96e5fc7 \ + --hash=sha256:f7d4a670107d75dfe5ad080bed6c341d18c4442f9378c9f58e5851e86eb79965 \ + --hash=sha256:f914c03e6a31deb632e2daa881fe198461f4d06e57ac3d0e05bbcab8eae01945 \ + --hash=sha256:fb66442c2546446944437df74379e9cf9e9db353e61301d1a0e26482f43f0dd8 # via # python3-saml # xmlsec -markdown==3.7 +markdown==3.7 \ + --hash=sha256:2ae2471477cfd02dbbf038d5d9bc226d40def84b4fe2986e49b59b6b472bbed2 \ + --hash=sha256:7eb6df5690b81a1d7942992c97fad2938e956e79df20cbc6186e9c3a77b1c803 # via django-markdownify -markupsafe==3.0.2 +markupsafe==3.0.2 \ + --hash=sha256:0bff5e0ae4ef2e1ae4fdf2dfd5b76c75e5c2fa4132d05fc1b0dabcd20c7e28c4 \ + --hash=sha256:0f4ca02bea9a23221c0182836703cbf8930c5e9454bacce27e767509fa286a30 \ + --hash=sha256:1225beacc926f536dc82e45f8a4d68502949dc67eea90eab715dea3a21c1b5f0 \ + --hash=sha256:131a3c7689c85f5ad20f9f6fb1b866f402c445b220c19fe4308c0b147ccd2ad9 \ + --hash=sha256:15ab75ef81add55874e7ab7055e9c397312385bd9ced94920f2802310c930396 \ + --hash=sha256:1a9d3f5f0901fdec14d8d2f66ef7d035f2157240a433441719ac9a3fba440b13 \ + --hash=sha256:1c99d261bd2d5f6b59325c92c73df481e05e57f19837bdca8413b9eac4bd8028 \ + --hash=sha256:1e084f686b92e5b83186b07e8a17fc09e38fff551f3602b249881fec658d3eca \ + --hash=sha256:2181e67807fc2fa785d0592dc2d6206c019b9502410671cc905d132a92866557 \ + --hash=sha256:2cb8438c3cbb25e220c2ab33bb226559e7afb3baec11c4f218ffa7308603c832 \ + --hash=sha256:3169b1eefae027567d1ce6ee7cae382c57fe26e82775f460f0b2778beaad66c0 \ + --hash=sha256:3809ede931876f5b2ec92eef964286840ed3540dadf803dd570c3b7e13141a3b \ + --hash=sha256:38a9ef736c01fccdd6600705b09dc574584b89bea478200c5fbf112a6b0d5579 \ + --hash=sha256:3d79d162e7be8f996986c064d1c7c817f6df3a77fe3d6859f6f9e7be4b8c213a \ + --hash=sha256:444dcda765c8a838eaae23112db52f1efaf750daddb2d9ca300bcae1039adc5c \ + --hash=sha256:48032821bbdf20f5799ff537c7ac3d1fba0ba032cfc06194faffa8cda8b560ff \ + --hash=sha256:4aa4e5faecf353ed117801a068ebab7b7e09ffb6e1d5e412dc852e0da018126c \ + --hash=sha256:52305740fe773d09cffb16f8ed0427942901f00adedac82ec8b67752f58a1b22 \ + --hash=sha256:569511d3b58c8791ab4c2e1285575265991e6d8f8700c7be0e88f86cb0672094 \ + --hash=sha256:57cb5a3cf367aeb1d316576250f65edec5bb3be939e9247ae594b4bcbc317dfb \ + --hash=sha256:5b02fb34468b6aaa40dfc198d813a641e3a63b98c2b05a16b9f80b7ec314185e \ + --hash=sha256:6381026f158fdb7c72a168278597a5e3a5222e83ea18f543112b2662a9b699c5 \ + --hash=sha256:6af100e168aa82a50e186c82875a5893c5597a0c1ccdb0d8b40240b1f28b969a \ + --hash=sha256:6c89876f41da747c8d3677a2b540fb32ef5715f97b66eeb0c6b66f5e3ef6f59d \ + --hash=sha256:6e296a513ca3d94054c2c881cc913116e90fd030ad1c656b3869762b754f5f8a \ + --hash=sha256:70a87b411535ccad5ef2f1df5136506a10775d267e197e4cf531ced10537bd6b \ + --hash=sha256:7e94c425039cde14257288fd61dcfb01963e658efbc0ff54f5306b06054700f8 \ + --hash=sha256:846ade7b71e3536c4e56b386c2a47adf5741d2d8b94ec9dc3e92e5e1ee1e2225 \ + --hash=sha256:88416bd1e65dcea10bc7569faacb2c20ce071dd1f87539ca2ab364bf6231393c \ + --hash=sha256:88b49a3b9ff31e19998750c38e030fc7bb937398b1f78cfa599aaef92d693144 \ + --hash=sha256:8c4e8c3ce11e1f92f6536ff07154f9d49677ebaaafc32db9db4620bc11ed480f \ + --hash=sha256:8e06879fc22a25ca47312fbe7c8264eb0b662f6db27cb2d3bbbc74b1df4b9b87 \ + --hash=sha256:9025b4018f3a1314059769c7bf15441064b2207cb3f065e6ea1e7359cb46db9d \ + --hash=sha256:93335ca3812df2f366e80509ae119189886b0f3c2b81325d39efdb84a1e2ae93 \ + --hash=sha256:9778bd8ab0a994ebf6f84c2b949e65736d5575320a17ae8984a77fab08db94cf \ + --hash=sha256:9e2d922824181480953426608b81967de705c3cef4d1af983af849d7bd619158 \ + --hash=sha256:a123e330ef0853c6e822384873bef7507557d8e4a082961e1defa947aa59ba84 \ + --hash=sha256:a904af0a6162c73e3edcb969eeeb53a63ceeb5d8cf642fade7d39e7963a22ddb \ + --hash=sha256:ad10d3ded218f1039f11a75f8091880239651b52e9bb592ca27de44eed242a48 \ + --hash=sha256:b424c77b206d63d500bcb69fa55ed8d0e6a3774056bdc4839fc9298a7edca171 \ + --hash=sha256:b5a6b3ada725cea8a5e634536b1b01c30bcdcd7f9c6fff4151548d5bf6b3a36c \ + --hash=sha256:ba8062ed2cf21c07a9e295d5b8a2a5ce678b913b45fdf68c32d95d6c1291e0b6 \ + --hash=sha256:ba9527cdd4c926ed0760bc301f6728ef34d841f405abf9d4f959c478421e4efd \ + --hash=sha256:bbcb445fa71794da8f178f0f6d66789a28d7319071af7a496d4d507ed566270d \ + --hash=sha256:bcf3e58998965654fdaff38e58584d8937aa3096ab5354d493c77d1fdd66d7a1 \ + --hash=sha256:c0ef13eaeee5b615fb07c9a7dadb38eac06a0608b41570d8ade51c56539e509d \ + --hash=sha256:cabc348d87e913db6ab4aa100f01b08f481097838bdddf7c7a84b7575b7309ca \ + --hash=sha256:cdb82a876c47801bb54a690c5ae105a46b392ac6099881cdfb9f6e95e4014c6a \ + --hash=sha256:cfad01eed2c2e0c01fd0ecd2ef42c492f7f93902e39a42fc9ee1692961443a29 \ + --hash=sha256:d16a81a06776313e817c951135cf7340a3e91e8c1ff2fac444cfd75fffa04afe \ + --hash=sha256:d8213e09c917a951de9d09ecee036d5c7d36cb6cb7dbaece4c71a60d79fb9798 \ + --hash=sha256:e07c3764494e3776c602c1e78e298937c3315ccc9043ead7e685b7f2b8d47b3c \ + --hash=sha256:e17c96c14e19278594aa4841ec148115f9c7615a47382ecb6b82bd8fea3ab0c8 \ + --hash=sha256:e444a31f8db13eb18ada366ab3cf45fd4b31e4db1236a4448f68778c1d1a5a2f \ + --hash=sha256:e6a2a455bd412959b57a172ce6328d2dd1f01cb2135efda2e4576e8a23fa3b0f \ + --hash=sha256:eaa0a10b7f72326f1372a713e73c3f739b524b3af41feb43e4921cb529f5929a \ + --hash=sha256:eb7972a85c54febfb25b5c4b4f3af4dcc731994c7da0d8a0b4a6eb0640e1d178 \ + --hash=sha256:ee55d3edf80167e48ea11a923c7386f4669df67d7994554387f84e7d8b0a2bf0 \ + --hash=sha256:f3818cb119498c0678015754eba762e0d61e5b52d34c8b13d770f0719f7b1d79 \ + --hash=sha256:f8b3d067f2e40fe93e1ccdd6b2e1d16c43140e76f02fb1319a05cf2b79d99430 \ + --hash=sha256:fcabf5ff6eea076f859677f5f0b6b5c1a51e70a376b0579e0eadef8db48c6b50 # via jinja2 -oauthlib==3.2.2 +oauthlib==3.2.2 \ + --hash=sha256:8139f29aac13e25d502680e9e19963e83f16838d48a0d71c287fe40e7067fbca \ + --hash=sha256:9859c40929662bec5d64f34d01c99e093149682a3f38915dc0655d5a633dd918 # via requests-oauthlib -openpyxl==3.1.5 +openpyxl==3.1.5 \ + --hash=sha256:5282c12b107bffeef825f4617dc029afaf41d0ea60823bbb665ef3079dc79de2 \ + --hash=sha256:cf0e3cf56142039133628b5acffe8ef0c12bc902d2aadd3e0fe5878dc08d1050 # via tablib -opentelemetry-api==1.29.0 +opentelemetry-api==1.29.0 \ + --hash=sha256:5fcd94c4141cc49c736271f3e1efb777bebe9cc535759c54c936cca4f1b312b8 \ + --hash=sha256:d04a6cf78aad09614f52964ecb38021e248f5714dc32c2e0d8fd99517b4d69cf # via # -r src/backend/requirements.in # opentelemetry-exporter-otlp-proto-grpc @@ -215,41 +997,65 @@ opentelemetry-api==1.29.0 # opentelemetry-instrumentation-wsgi # opentelemetry-sdk # opentelemetry-semantic-conventions -opentelemetry-exporter-otlp==1.29.0 +opentelemetry-exporter-otlp==1.29.0 \ + --hash=sha256:b8da6e20f5b0ffe604154b1e16a407eade17ce310c42fb85bb4e1246fc3688ad \ + --hash=sha256:ee7dfcccbb5e87ad9b389908452e10b7beeab55f70a83f41ce5b8c4efbde6544 # via -r src/backend/requirements.in -opentelemetry-exporter-otlp-proto-common==1.29.0 +opentelemetry-exporter-otlp-proto-common==1.29.0 \ + --hash=sha256:a9d7376c06b4da9cf350677bcddb9618ed4b8255c3f6476975f5e38274ecd3aa \ + --hash=sha256:e7c39b5dbd1b78fe199e40ddfe477e6983cb61aa74ba836df09c3869a3e3e163 # via # opentelemetry-exporter-otlp-proto-grpc # opentelemetry-exporter-otlp-proto-http -opentelemetry-exporter-otlp-proto-grpc==1.29.0 +opentelemetry-exporter-otlp-proto-grpc==1.29.0 \ + --hash=sha256:3d324d07d64574d72ed178698de3d717f62a059a93b6b7685ee3e303384e73ea \ + --hash=sha256:5a2a3a741a2543ed162676cf3eefc2b4150e6f4f0a193187afb0d0e65039c69c # via opentelemetry-exporter-otlp -opentelemetry-exporter-otlp-proto-http==1.29.0 +opentelemetry-exporter-otlp-proto-http==1.29.0 \ + --hash=sha256:b10d174e3189716f49d386d66361fbcf6f2b9ad81e05404acdee3f65c8214204 \ + --hash=sha256:b228bdc0f0cfab82eeea834a7f0ffdd2a258b26aa33d89fb426c29e8e934d9d0 # via opentelemetry-exporter-otlp -opentelemetry-instrumentation==0.50b0 +opentelemetry-instrumentation==0.50b0 \ + --hash=sha256:7d98af72de8dec5323e5202e46122e5f908592b22c6d24733aad619f07d82979 \ + --hash=sha256:b8f9fc8812de36e1c6dffa5bfc6224df258841fb387b6dfe5df15099daa10630 # via # opentelemetry-instrumentation-django # opentelemetry-instrumentation-redis # opentelemetry-instrumentation-requests # opentelemetry-instrumentation-wsgi -opentelemetry-instrumentation-django==0.50b0 +opentelemetry-instrumentation-django==0.50b0 \ + --hash=sha256:624fd0beb1ac827f2af31709c2da5cb55d8dc899c2449d6e8fcc9fa5538fd56b \ + --hash=sha256:ab7b4cd52b8f12420d968823f6bbfbc2a6ddb2af7a05fcb0d5b6755d338f1915 # via -r src/backend/requirements.in -opentelemetry-instrumentation-redis==0.50b0 +opentelemetry-instrumentation-redis==0.50b0 \ + --hash=sha256:48c115189781a4eb1513457f4cb03f7c28bac45d4ca619802d0fec5d08db9e0f \ + --hash=sha256:ab5c983acdd2d4dd897b8d0f7c28d4fd548458259895830e43d9a206f4afa391 # via -r src/backend/requirements.in -opentelemetry-instrumentation-requests==0.50b0 +opentelemetry-instrumentation-requests==0.50b0 \ + --hash=sha256:2c60a890988d6765de9230004d0af9071b3b2e1ddba4ca3b631cfb8a1722208d \ + --hash=sha256:f8088c76f757985b492aad33331d21aec2f99c197472a57091c2e986a4b7ec8b # via -r src/backend/requirements.in -opentelemetry-instrumentation-wsgi==0.50b0 +opentelemetry-instrumentation-wsgi==0.50b0 \ + --hash=sha256:4bc0fdf52b603507d6170a25504f0ceea358d7e90a2c0e8794b7b7eca5ea355c \ + --hash=sha256:c25b5f1b664d984a41546a34cf2f893dcde6cf56922f88c475864e7df37edf4a # via opentelemetry-instrumentation-django -opentelemetry-proto==1.29.0 +opentelemetry-proto==1.29.0 \ + --hash=sha256:3c136aa293782e9b44978c738fff72877a4b78b5d21a64e879898db7b2d93e5d \ + --hash=sha256:495069c6f5495cbf732501cdcd3b7f60fda2b9d3d4255706ca99b7ca8dec53ff # via # opentelemetry-exporter-otlp-proto-common # opentelemetry-exporter-otlp-proto-grpc # opentelemetry-exporter-otlp-proto-http -opentelemetry-sdk==1.29.0 +opentelemetry-sdk==1.29.0 \ + --hash=sha256:173be3b5d3f8f7d671f20ea37056710217959e774e2749d984355d1f9391a30a \ + --hash=sha256:b0787ce6aade6ab84315302e72bd7a7f2f014b0fb1b7c3295b88afe014ed0643 # via # -r src/backend/requirements.in # opentelemetry-exporter-otlp-proto-grpc # opentelemetry-exporter-otlp-proto-http -opentelemetry-semantic-conventions==0.50b0 +opentelemetry-semantic-conventions==0.50b0 \ + --hash=sha256:02dc6dbcb62f082de9b877ff19a3f1ffaa3c306300fa53bfac761c4567c83d38 \ + --hash=sha256:e87efba8fdb67fb38113efea6a349531e75ed7ffc01562f65b802fcecb5e115e # via # opentelemetry-instrumentation # opentelemetry-instrumentation-django @@ -257,18 +1063,95 @@ opentelemetry-semantic-conventions==0.50b0 # opentelemetry-instrumentation-requests # opentelemetry-instrumentation-wsgi # opentelemetry-sdk -opentelemetry-util-http==0.50b0 +opentelemetry-util-http==0.50b0 \ + --hash=sha256:21f8aedac861ffa3b850f8c0a6c373026189eb8630ac6e14a2bf8c55695cc090 \ + --hash=sha256:dc4606027e1bc02aabb9533cc330dd43f874fca492e4175c31d7154f341754af # via # opentelemetry-instrumentation-django # opentelemetry-instrumentation-requests # opentelemetry-instrumentation-wsgi -packaging==24.2 +packaging==24.2 \ + --hash=sha256:09abb1bccd265c01f4a3aa3f7a7db064b36514d2cba19a2f694fe6150451a759 \ + --hash=sha256:c228a6dc5e932d346bc5739379109d49e8853dd8223571c7c5b55260edc0b97f # via # gunicorn # opentelemetry-instrumentation -pdf2image==1.17.0 +pdf2image==1.17.0 \ + --hash=sha256:eaa959bc116b420dd7ec415fcae49b98100dda3dd18cd2fdfa86d09f112f6d57 \ + --hash=sha256:ecdd58d7afb810dffe21ef2b1bbc057ef434dabbac6c33778a38a3f7744a27e2 # via -r src/backend/requirements.in -pillow==11.1.0 +pillow==11.1.0 \ + --hash=sha256:015c6e863faa4779251436db398ae75051469f7c903b043a48f078e437656f83 \ + --hash=sha256:0a2f91f8a8b367e7a57c6e91cd25af510168091fb89ec5146003e424e1558a96 \ + --hash=sha256:11633d58b6ee5733bde153a8dafd25e505ea3d32e261accd388827ee987baf65 \ + --hash=sha256:2062ffb1d36544d42fcaa277b069c88b01bb7298f4efa06731a7fd6cc290b81a \ + --hash=sha256:31eba6bbdd27dde97b0174ddf0297d7a9c3a507a8a1480e1e60ef914fe23d352 \ + --hash=sha256:3362c6ca227e65c54bf71a5f88b3d4565ff1bcbc63ae72c34b07bbb1cc59a43f \ + --hash=sha256:368da70808b36d73b4b390a8ffac11069f8a5c85f29eff1f1b01bcf3ef5b2a20 \ + --hash=sha256:36ba10b9cb413e7c7dfa3e189aba252deee0602c86c309799da5a74009ac7a1c \ + --hash=sha256:3764d53e09cdedd91bee65c2527815d315c6b90d7b8b79759cc48d7bf5d4f114 \ + --hash=sha256:3a5fe20a7b66e8135d7fd617b13272626a28278d0e578c98720d9ba4b2439d49 \ + --hash=sha256:3cdcdb0b896e981678eee140d882b70092dac83ac1cdf6b3a60e2216a73f2b91 \ + --hash=sha256:4637b88343166249fe8aa94e7c4a62a180c4b3898283bb5d3d2fd5fe10d8e4e0 \ + --hash=sha256:4db853948ce4e718f2fc775b75c37ba2efb6aaea41a1a5fc57f0af59eee774b2 \ + --hash=sha256:4dd43a78897793f60766563969442020e90eb7847463eca901e41ba186a7d4a5 \ + --hash=sha256:54251ef02a2309b5eec99d151ebf5c9904b77976c8abdcbce7891ed22df53884 \ + --hash=sha256:54ce1c9a16a9561b6d6d8cb30089ab1e5eb66918cb47d457bd996ef34182922e \ + --hash=sha256:593c5fd6be85da83656b93ffcccc2312d2d149d251e98588b14fbc288fd8909c \ + --hash=sha256:5bb94705aea800051a743aa4874bb1397d4695fb0583ba5e425ee0328757f196 \ + --hash=sha256:67cd427c68926108778a9005f2a04adbd5e67c442ed21d95389fe1d595458756 \ + --hash=sha256:70ca5ef3b3b1c4a0812b5c63c57c23b63e53bc38e758b37a951e5bc466449861 \ + --hash=sha256:73ddde795ee9b06257dac5ad42fcb07f3b9b813f8c1f7f870f402f4dc54b5269 \ + --hash=sha256:758e9d4ef15d3560214cddbc97b8ef3ef86ce04d62ddac17ad39ba87e89bd3b1 \ + --hash=sha256:7d33d2fae0e8b170b6a6c57400e077412240f6f5bb2a342cf1ee512a787942bb \ + --hash=sha256:7fdadc077553621911f27ce206ffcbec7d3f8d7b50e0da39f10997e8e2bb7f6a \ + --hash=sha256:8000376f139d4d38d6851eb149b321a52bb8893a88dae8ee7d95840431977081 \ + --hash=sha256:837060a8599b8f5d402e97197d4924f05a2e0d68756998345c829c33186217b1 \ + --hash=sha256:89dbdb3e6e9594d512780a5a1c42801879628b38e3efc7038094430844e271d8 \ + --hash=sha256:8c730dc3a83e5ac137fbc92dfcfe1511ce3b2b5d7578315b63dbbb76f7f51d90 \ + --hash=sha256:8e275ee4cb11c262bd108ab2081f750db2a1c0b8c12c1897f27b160c8bd57bbc \ + --hash=sha256:9044b5e4f7083f209c4e35aa5dd54b1dd5b112b108648f5c902ad586d4f945c5 \ + --hash=sha256:93a18841d09bcdd774dcdc308e4537e1f867b3dec059c131fde0327899734aa1 \ + --hash=sha256:9409c080586d1f683df3f184f20e36fb647f2e0bc3988094d4fd8c9f4eb1b3b3 \ + --hash=sha256:96f82000e12f23e4f29346e42702b6ed9a2f2fea34a740dd5ffffcc8c539eb35 \ + --hash=sha256:9aa9aeddeed452b2f616ff5507459e7bab436916ccb10961c4a382cd3e03f47f \ + --hash=sha256:9ee85f0696a17dd28fbcfceb59f9510aa71934b483d1f5601d1030c3c8304f3c \ + --hash=sha256:a07dba04c5e22824816b2615ad7a7484432d7f540e6fa86af60d2de57b0fcee2 \ + --hash=sha256:a3cd561ded2cf2bbae44d4605837221b987c216cff94f49dfeed63488bb228d2 \ + --hash=sha256:a697cd8ba0383bba3d2d3ada02b34ed268cb548b369943cd349007730c92bddf \ + --hash=sha256:a76da0a31da6fcae4210aa94fd779c65c75786bc9af06289cd1c184451ef7a65 \ + --hash=sha256:a85b653980faad27e88b141348707ceeef8a1186f75ecc600c395dcac19f385b \ + --hash=sha256:a8d65b38173085f24bc07f8b6c505cbb7418009fa1a1fcb111b1f4961814a442 \ + --hash=sha256:aa8dd43daa836b9a8128dbe7d923423e5ad86f50a7a14dc688194b7be5c0dea2 \ + --hash=sha256:ab8a209b8485d3db694fa97a896d96dd6533d63c22829043fd9de627060beade \ + --hash=sha256:abc56501c3fd148d60659aae0af6ddc149660469082859fa7b066a298bde9482 \ + --hash=sha256:ad5db5781c774ab9a9b2c4302bbf0c1014960a0a7be63278d13ae6fdf88126fe \ + --hash=sha256:ae98e14432d458fc3de11a77ccb3ae65ddce70f730e7c76140653048c71bfcbc \ + --hash=sha256:b20be51b37a75cc54c2c55def3fa2c65bb94ba859dde241cd0a4fd302de5ae0a \ + --hash=sha256:b523466b1a31d0dcef7c5be1f20b942919b62fd6e9a9be199d035509cbefc0ec \ + --hash=sha256:b5d658fbd9f0d6eea113aea286b21d3cd4d3fd978157cbf2447a6035916506d3 \ + --hash=sha256:b6123aa4a59d75f06e9dd3dac5bf8bc9aa383121bb3dd9a7a612e05eabc9961a \ + --hash=sha256:bd165131fd51697e22421d0e467997ad31621b74bfc0b75956608cb2906dda07 \ + --hash=sha256:bf902d7413c82a1bfa08b06a070876132a5ae6b2388e2712aab3a7cbc02205c6 \ + --hash=sha256:c12fc111ef090845de2bb15009372175d76ac99969bdf31e2ce9b42e4b8cd88f \ + --hash=sha256:c1eec9d950b6fe688edee07138993e54ee4ae634c51443cfb7c1e7613322718e \ + --hash=sha256:c640e5a06869c75994624551f45e5506e4256562ead981cce820d5ab39ae2192 \ + --hash=sha256:cc1331b6d5a6e144aeb5e626f4375f5b7ae9934ba620c0ac6b3e43d5e683a0f0 \ + --hash=sha256:cfd5cd998c2e36a862d0e27b2df63237e67273f2fc78f47445b14e73a810e7e6 \ + --hash=sha256:d3d8da4a631471dfaf94c10c85f5277b1f8e42ac42bade1ac67da4b4a7359b73 \ + --hash=sha256:d44ff19eea13ae4acdaaab0179fa68c0c6f2f45d66a4d8ec1eda7d6cecbcc15f \ + --hash=sha256:dd0052e9db3474df30433f83a71b9b23bd9e4ef1de13d92df21a52c0303b8ab6 \ + --hash=sha256:dd0e081319328928531df7a0e63621caf67652c8464303fd102141b785ef9547 \ + --hash=sha256:dda60aa465b861324e65a78c9f5cf0f4bc713e4309f83bc387be158b077963d9 \ + --hash=sha256:e06695e0326d05b06833b40b7ef477e475d0b1ba3a6d27da1bb48c23209bf457 \ + --hash=sha256:e1abe69aca89514737465752b4bcaf8016de61b3be1397a8fc260ba33321b3a8 \ + --hash=sha256:e267b0ed063341f3e60acd25c05200df4193e15a4a5807075cd71225a2386e26 \ + --hash=sha256:e5449ca63da169a2e6068dd0e2fcc8d91f9558aba89ff6d02121ca8ab11e79e5 \ + --hash=sha256:e63e4e5081de46517099dc30abe418122f54531a6ae2ebc8680bcd7096860eab \ + --hash=sha256:f189805c8be5ca5add39e6f899e6ce2ed824e65fb45f3c28cb2841911da19070 \ + --hash=sha256:f7955ecf5609dee9442cbface754f2c6e541d9e6eda87fad7f7a989b0bdb9d71 \ + --hash=sha256:f86d3a7a9af5d826744fabf4afd15b9dfef44fe69a98541f666f66fbb8d3fef9 \ + --hash=sha256:fbd43429d0d7ed6533b25fc993861b8fd512c42d04514a0dd6337fb3ccf22761 # via # -r src/backend/requirements.in # django-stdimage @@ -276,113 +1159,464 @@ pillow==11.1.0 # python-barcode # qrcode # weasyprint -pint==0.24.4 +pint==0.24.4 \ + --hash=sha256:35275439b574837a6cd3020a5a4a73645eb125ce4152a73a2f126bf164b91b80 \ + --hash=sha256:aa54926c8772159fcf65f82cc0d34de6768c151b32ad1deb0331291c38fe7659 # via -r src/backend/requirements.in -pip-licenses==5.0.0 +pip-licenses==5.0.0 \ + --hash=sha256:0633a1f9aab58e5a6216931b0e1d5cdded8bcc2709ff563674eb0e2ff9e77e8e \ + --hash=sha256:82c83666753efb86d1af1c405c8ab273413eb10d6689c218df2f09acf40e477d # via -r src/backend/requirements.in -platformdirs==4.3.6 +platformdirs==4.3.6 \ + --hash=sha256:357fb2acbc885b0419afd3ce3ed34564c13c9b95c89360cd9563f73aa5e2b907 \ + --hash=sha256:73e575e1408ab8103900836b97580d5307456908a03e92031bab39e4554cc3fb # via pint -ppf-datamatrix==0.2 +ppf-datamatrix==0.2 \ + --hash=sha256:819be65eae444b760e178d5761853f78f8e5fca14fec2809b5e3369978fa9244 \ + --hash=sha256:8f034d9c90e408f60f8b10a273baab81014c9a81c983dc1ebdc31d4ca5ac5582 # via -r src/backend/requirements.in -prettytable==3.14.0 +prettytable==3.12.0 \ + --hash=sha256:77ca0ad1c435b6e363d7e8623d7cc4fcf2cf15513bf77a1c1b2e814930ac57cc \ + --hash=sha256:f04b3e1ba35747ac86e96ec33e3bb9748ce08e254dc2a1c6253945901beec804 # via pip-licenses -protobuf==5.29.3 +protobuf==5.29.3 \ + --hash=sha256:0a18ed4a24198528f2333802eb075e59dea9d679ab7a6c5efb017a59004d849f \ + --hash=sha256:0eb32bfa5219fc8d4111803e9a690658aa2e6366384fd0851064b963b6d1f2a7 \ + --hash=sha256:3ea51771449e1035f26069c4c7fd51fba990d07bc55ba80701c78f886bf9c888 \ + --hash=sha256:5da0f41edaf117bde316404bad1a486cb4ededf8e4a54891296f648e8e076620 \ + --hash=sha256:6ce8cc3389a20693bfde6c6562e03474c40851b44975c9b2bf6df7d8c4f864da \ + --hash=sha256:84a57163a0ccef3f96e4b6a20516cedcf5bb3a95a657131c5c3ac62200d23252 \ + --hash=sha256:a4fa6f80816a9a0678429e84973f2f98cbc218cca434abe8db2ad0bffc98503a \ + --hash=sha256:a8434404bbf139aa9e1300dbf989667a83d42ddda9153d8ab76e0d5dcaca484e \ + --hash=sha256:b89c115d877892a512f79a8114564fb435943b59067615894c3b13cd3e1fa107 \ + --hash=sha256:c027e08a08be10b67c06bf2370b99c811c466398c357e615ca88c91c07f0910f \ + --hash=sha256:daaf63f70f25e8689c072cfad4334ca0ac1d1e05a92fc15c54eb9cf23c3efd84 # via # googleapis-common-protos # opentelemetry-proto -py-moneyed==3.0 +py-moneyed==3.0 \ + --hash=sha256:4906f0f02cf2b91edba2e156f2d4e9a78f224059ab8c8fa2ff26230c75d894e8 \ + --hash=sha256:9583a14f99c05b46196193d8185206e9b73c8439fc8a5eee9cfc7e733676d9bb # via django-money -pycparser==2.22 +pycparser==2.22 \ + --hash=sha256:491c8be9c040f5390f5bf44a5b07752bd07f56edf992381b05c701439eec10f6 \ + --hash=sha256:c3702b6d3dd8c7abc1afa565d7e63d53a1d0bd86cdc24edd75470f4de499cfcc # via cffi -pydyf==0.10.0 +pydyf==0.10.0 \ + --hash=sha256:357194593efaf61d7b48ab97c3d59722114934967c3df3d7878ca6dd25b04c30 \ + --hash=sha256:ef76b6c0976a091a9e15827fb5800e5e37e7cd1a3ca4d4bd19d10a14ea8c0ae3 # via # -r src/backend/requirements.in # weasyprint -pyjwt[crypto]==2.10.1 +pyjwt[crypto]==2.10.1 \ + --hash=sha256:3cc5772eb20009233caf06e9d8a0577824723b44e6648ee0a2aedb6cf9381953 \ + --hash=sha256:dcdd193e30abefd5debf142f9adfcdd2b58004e644f25406ffaebd50bd98dacb # via # django-allauth # djangorestframework-simplejwt -pyphen==0.17.2 +pyphen==0.17.0 \ + --hash=sha256:1d13acd1ce37a384d7612954ae6c7801bb4c5316da0e2b937b2127ba702a3da4 \ + --hash=sha256:dad0b2e4aa80f6d70bf06711b2da36c47a756b933c1d0c4cbbab40f643a5958c # via weasyprint -python-barcode[images]==0.15.1 +python-barcode[images]==0.15.1 \ + --hash=sha256:057636fba37369c22852410c8535b36adfbeb965ddfd4e5b6924455d692e0886 \ + --hash=sha256:3b1825fbdb11e597466dff4286b4ea9b1e86a57717b59e563ae679726fc854de # via -r src/backend/requirements.in -python-dateutil==2.9.0.post0 +python-dateutil==2.9.0.post0 \ + --hash=sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3 \ + --hash=sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427 # via # django-recurrence # icalendar -python-dotenv==1.0.1 +python-dotenv==1.0.1 \ + --hash=sha256:e324ee90a023d808f1959c46bcbc04446a10ced277783dc6ee09987c37ec10ca \ + --hash=sha256:f7b63ef50f1b690dddf550d03497b66d609393b40b564ed0d674909a68ebf16a # via -r src/backend/requirements.in -python-fsutil==0.14.1 +python-fsutil==0.14.1 \ + --hash=sha256:0d45e623f0f4403f674bdd8ae7aa7d24a4b3132ea45c65416bd2865e6b20b035 \ + --hash=sha256:8fb204fa8059f37bdeee8a1dc0fff010170202ea47c4225ee71bb3c26f3997be # via django-maintenance-mode -python-ipware==3.0.0 +python-ipware==3.0.0 \ + --hash=sha256:9117b1c4dddcb5d5ca49e6a9617de2fc66aec2ef35394563ac4eecabdf58c062 \ + --hash=sha256:fc936e6e7ec9fcc107f9315df40658f468ac72f739482a707181742882e36b60 # via django-ipware -python3-openid==3.2.0 +python3-openid==3.2.0 \ + --hash=sha256:33fbf6928f401e0b790151ed2b5290b02545e8775f982485205a066f874aaeaf \ + --hash=sha256:6626f771e0417486701e0b4daff762e7212e820ca5b29fcc0d05f6f8736dfa6b # via django-allauth -python3-saml==1.16.0 +python3-saml==1.16.0 \ + --hash=sha256:20b97d11b04f01ee22e98f4a38242e2fea2e28fbc7fbc9bdd57cab5ac7fc2d0d \ + --hash=sha256:97c9669aecabc283c6e5fb4eb264f446b6e006f5267d01c9734f9d8bffdac133 \ + --hash=sha256:c49097863c278ff669a337a96c46dc1f25d16307b4bb2679d2d1733cc4f5176a # via django-allauth -pytz==2025.1 +pytz==2024.2 \ + --hash=sha256:2aa355083c50a0f93fa581709deac0c9ad65cca8a9e9beac660adcbd493c798a \ + --hash=sha256:31c7c1817eb7fae7ca4b8c7ee50c72f93aa2dd863de768e1ef4245d426aa0725 # via # django-dbbackup # djangorestframework -pyyaml==6.0.2 +pyyaml==6.0.2 \ + --hash=sha256:01179a4a8559ab5de078078f37e5c1a30d76bb88519906844fd7bdea1b7729ff \ + --hash=sha256:0833f8694549e586547b576dcfaba4a6b55b9e96098b36cdc7ebefe667dfed48 \ + --hash=sha256:0a9a2848a5b7feac301353437eb7d5957887edbf81d56e903999a75a3d743086 \ + --hash=sha256:0b69e4ce7a131fe56b7e4d770c67429700908fc0752af059838b1cfb41960e4e \ + --hash=sha256:0ffe8360bab4910ef1b9e87fb812d8bc0a308b0d0eef8c8f44e0254ab3b07133 \ + --hash=sha256:11d8f3dd2b9c1207dcaf2ee0bbbfd5991f571186ec9cc78427ba5bd32afae4b5 \ + --hash=sha256:17e311b6c678207928d649faa7cb0d7b4c26a0ba73d41e99c4fff6b6c3276484 \ + --hash=sha256:1e2120ef853f59c7419231f3bf4e7021f1b936f6ebd222406c3b60212205d2ee \ + --hash=sha256:1f71ea527786de97d1a0cc0eacd1defc0985dcf6b3f17bb77dcfc8c34bec4dc5 \ + --hash=sha256:23502f431948090f597378482b4812b0caae32c22213aecf3b55325e049a6c68 \ + --hash=sha256:24471b829b3bf607e04e88d79542a9d48bb037c2267d7927a874e6c205ca7e9a \ + --hash=sha256:29717114e51c84ddfba879543fb232a6ed60086602313ca38cce623c1d62cfbf \ + --hash=sha256:2e99c6826ffa974fe6e27cdb5ed0021786b03fc98e5ee3c5bfe1fd5015f42b99 \ + --hash=sha256:39693e1f8320ae4f43943590b49779ffb98acb81f788220ea932a6b6c51004d8 \ + --hash=sha256:3ad2a3decf9aaba3d29c8f537ac4b243e36bef957511b4766cb0057d32b0be85 \ + --hash=sha256:3b1fdb9dc17f5a7677423d508ab4f243a726dea51fa5e70992e59a7411c89d19 \ + --hash=sha256:41e4e3953a79407c794916fa277a82531dd93aad34e29c2a514c2c0c5fe971cc \ + --hash=sha256:43fa96a3ca0d6b1812e01ced1044a003533c47f6ee8aca31724f78e93ccc089a \ + --hash=sha256:50187695423ffe49e2deacb8cd10510bc361faac997de9efef88badc3bb9e2d1 \ + --hash=sha256:5ac9328ec4831237bec75defaf839f7d4564be1e6b25ac710bd1a96321cc8317 \ + --hash=sha256:5d225db5a45f21e78dd9358e58a98702a0302f2659a3c6cd320564b75b86f47c \ + --hash=sha256:6395c297d42274772abc367baaa79683958044e5d3835486c16da75d2a694631 \ + --hash=sha256:688ba32a1cffef67fd2e9398a2efebaea461578b0923624778664cc1c914db5d \ + --hash=sha256:68ccc6023a3400877818152ad9a1033e3db8625d899c72eacb5a668902e4d652 \ + --hash=sha256:70b189594dbe54f75ab3a1acec5f1e3faa7e8cf2f1e08d9b561cb41b845f69d5 \ + --hash=sha256:797b4f722ffa07cc8d62053e4cff1486fa6dc094105d13fea7b1de7d8bf71c9e \ + --hash=sha256:7c36280e6fb8385e520936c3cb3b8042851904eba0e58d277dca80a5cfed590b \ + --hash=sha256:7e7401d0de89a9a855c839bc697c079a4af81cf878373abd7dc625847d25cbd8 \ + --hash=sha256:80bab7bfc629882493af4aa31a4cfa43a4c57c83813253626916b8c7ada83476 \ + --hash=sha256:82d09873e40955485746739bcb8b4586983670466c23382c19cffecbf1fd8706 \ + --hash=sha256:8388ee1976c416731879ac16da0aff3f63b286ffdd57cdeb95f3f2e085687563 \ + --hash=sha256:8824b5a04a04a047e72eea5cec3bc266db09e35de6bdfe34c9436ac5ee27d237 \ + --hash=sha256:8b9c7197f7cb2738065c481a0461e50ad02f18c78cd75775628afb4d7137fb3b \ + --hash=sha256:9056c1ecd25795207ad294bcf39f2db3d845767be0ea6e6a34d856f006006083 \ + --hash=sha256:936d68689298c36b53b29f23c6dbb74de12b4ac12ca6cfe0e047bedceea56180 \ + --hash=sha256:9b22676e8097e9e22e36d6b7bda33190d0d400f345f23d4065d48f4ca7ae0425 \ + --hash=sha256:a4d3091415f010369ae4ed1fc6b79def9416358877534caf6a0fdd2146c87a3e \ + --hash=sha256:a8786accb172bd8afb8be14490a16625cbc387036876ab6ba70912730faf8e1f \ + --hash=sha256:a9f8c2e67970f13b16084e04f134610fd1d374bf477b17ec1599185cf611d725 \ + --hash=sha256:bc2fa7c6b47d6bc618dd7fb02ef6fdedb1090ec036abab80d4681424b84c1183 \ + --hash=sha256:c70c95198c015b85feafc136515252a261a84561b7b1d51e3384e0655ddf25ab \ + --hash=sha256:cc1c1159b3d456576af7a3e4d1ba7e6924cb39de8f67111c735f6fc832082774 \ + --hash=sha256:ce826d6ef20b1bc864f0a68340c8b3287705cae2f8b4b1d932177dcc76721725 \ + --hash=sha256:d584d9ec91ad65861cc08d42e834324ef890a082e591037abe114850ff7bbc3e \ + --hash=sha256:d7fded462629cfa4b685c5416b949ebad6cec74af5e2d42905d41e257e0869f5 \ + --hash=sha256:d84a1718ee396f54f3a086ea0a66d8e552b2ab2017ef8b420e92edbc841c352d \ + --hash=sha256:d8e03406cac8513435335dbab54c0d385e4a49e4945d2909a581c83647ca0290 \ + --hash=sha256:e10ce637b18caea04431ce14fabcf5c64a1c61ec9c56b071a4b7ca131ca52d44 \ + --hash=sha256:ec031d5d2feb36d1d1a24380e4db6d43695f3748343d99434e6f5f9156aaa2ed \ + --hash=sha256:ef6107725bd54b262d6dedcc2af448a266975032bc85ef0172c5f059da6325b4 \ + --hash=sha256:efdca5630322a10774e8e98e1af481aad470dd62c3170801852d752aa7a783ba \ + --hash=sha256:f753120cb8181e736c57ef7636e83f31b9c0d1722c516f7e86cf15b7aa57ff12 \ + --hash=sha256:ff3824dc5261f50c9b0dfb3be22b4567a6f938ccce4587b38952d85fd9e9afe4 # via # -r src/backend/requirements.in # drf-spectacular # tablib -qrcode[pil]==8.0 +qrcode[pil]==8.0 \ + --hash=sha256:025ce2b150f7fe4296d116ee9bad455a6643ab4f6e7dce541613a4758cbce347 \ + --hash=sha256:9fc05f03305ad27a709eb742cf3097fa19e6f6f93bb9e2f039c0979190f6f1b1 # via # -r src/backend/requirements.in # django-allauth -rapidfuzz==3.12.1 +rapidfuzz==3.11.0 \ + --hash=sha256:0b488b244931d0291412917e6e46ee9f6a14376625e150056fe7c4426ef28225 \ + --hash=sha256:1315cd2a351144572e31fe3df68340d4b83ddec0af8b2e207cd32930c6acd037 \ + --hash=sha256:1bac4873f6186f5233b0084b266bfb459e997f4c21fc9f029918f44a9eccd304 \ + --hash=sha256:1cb1965a28b0fa64abdee130c788a0bc0bb3cf9ef7e3a70bf055c086c14a3d7e \ + --hash=sha256:22033677982b9c4c49676f215b794b0404073f8974f98739cb7234e4a9ade9ad \ + --hash=sha256:231c8b2efbd7f8d2ecd1ae900363ba168b8870644bb8f2b5aa96e4a7573bde19 \ + --hash=sha256:25398d9ac7294e99876a3027ffc52c6bebeb2d702b1895af6ae9c541ee676702 \ + --hash=sha256:2c87319b0ab9d269ab84f6453601fd49b35d9e4a601bbaef43743f26fabf496c \ + --hash=sha256:3048c6ed29d693fba7d2a7caf165f5e0bb2b9743a0989012a98a47b975355cca \ + --hash=sha256:339607394941801e6e3f6c1ecd413a36e18454e7136ed1161388de674f47f9d9 \ + --hash=sha256:3794df87313dfb56fafd679b962e0613c88a293fd9bd5dd5c2793d66bf06a101 \ + --hash=sha256:3857e335f97058c4b46fa39ca831290b70de554a5c5af0323d2f163b19c5f2a6 \ + --hash=sha256:3871fa7dfcef00bad3c7e8ae8d8fd58089bad6fb21f608d2bf42832267ca9663 \ + --hash=sha256:3f28952da055dbfe75828891cd3c9abf0984edc8640573c18b48c14c68ca5e06 \ + --hash=sha256:42f4dd264ada7a9aa0805ea0da776dc063533917773cf2df5217f14eb4429eae \ + --hash=sha256:4416ca69af933d4a8ad30910149d3db6d084781d5c5fdedb713205389f535385 \ + --hash=sha256:4469307f464ae3089acf3210b8fc279110d26d10f79e576f385a98f4429f7d97 \ + --hash=sha256:4513dd01cee11e354c31b75f652d4d466c9440b6859f84e600bdebfccb17735a \ + --hash=sha256:45b15b8a118856ac9caac6877f70f38b8a0d310475d50bc814698659eabc1cdb \ + --hash=sha256:494eef2c68305ab75139034ea25328a04a548d297712d9cf887bf27c158c388b \ + --hash=sha256:4d0d26c7172bdb64f86ee0765c5b26ea1dc45c52389175888ec073b9b28f4305 \ + --hash=sha256:4f9f12c2d0aa52b86206d2059916153876a9b1cf9dfb3cf2f344913167f1c3d4 \ + --hash=sha256:51f24cb39e64256221e6952f22545b8ce21cacd59c0d3e367225da8fc4b868d8 \ + --hash=sha256:54e7f442fb9cca81e9df32333fb075ef729052bcabe05b0afc0441f462299114 \ + --hash=sha256:5a167344c1d6db06915fb0225592afdc24d8bafaaf02de07d4788ddd37f4bc2f \ + --hash=sha256:5b659e1e2ea2784a9a397075a7fc395bfa4fe66424042161c4bcaf6e4f637b38 \ + --hash=sha256:5bb636b0150daa6d3331b738f7c0f8b25eadc47f04a40e5c23c4bfb4c4e20ae3 \ + --hash=sha256:5e8ea35f2419c7d56b3e75fbde2698766daedb374f20eea28ac9b1f668ef4f74 \ + --hash=sha256:5e8f93bc736020351a6f8e71666e1f486bb8bd5ce8112c443a30c77bfde0eb68 \ + --hash=sha256:62171b270ecc4071be1c1f99960317db261d4c8c83c169e7f8ad119211fe7397 \ + --hash=sha256:6668321f90aa02a5a789d4e16058f2e4f2692c5230252425c3532a8a62bc3424 \ + --hash=sha256:6ad02bab756751c90fa27f3069d7b12146613061341459abf55f8190d899649f \ + --hash=sha256:6b01c1ddbb054283797967ddc5433d5c108d680e8fa2684cf368be05407b07e4 \ + --hash=sha256:714a7ba31ba46b64d30fccfe95f8013ea41a2e6237ba11a805a27cdd3bce2573 \ + --hash=sha256:76a4a11ba8f678c9e5876a7d465ab86def047a4fcc043617578368755d63a1bc \ + --hash=sha256:7864e80a0d4e23eb6194254a81ee1216abdc53f9dc85b7f4d56668eced022eb8 \ + --hash=sha256:82497f244aac10b20710448645f347d862364cc4f7d8b9ba14bd66b5ce4dec18 \ + --hash=sha256:84819390a36d6166cec706b9d8f0941f115f700b7faecab5a7e22fc367408bc3 \ + --hash=sha256:8724a978f8af7059c5323d523870bf272a097478e1471295511cf58b2642ff83 \ + --hash=sha256:8b63cb1f2eb371ef20fb155e95efd96e060147bdd4ab9fc400c97325dfee9fe1 \ + --hash=sha256:8c7af25bda96ac799378ac8aba54a8ece732835c7b74cfc201b688a87ed11152 \ + --hash=sha256:8dd501de6f7a8f83557d20613b58734d1cb5f0be78d794cde64fe43cfc63f5f2 \ + --hash=sha256:8ed59044aea9eb6c663112170f2399b040d5d7b162828b141f2673e822093fa8 \ + --hash=sha256:906f1f2a1b91c06599b3dd1be207449c5d4fc7bd1e1fa2f6aef161ea6223f165 \ + --hash=sha256:92ebb7c12f682b5906ed98429f48a3dd80dd0f9721de30c97a01473d1a346576 \ + --hash=sha256:99aebef8268f2bc0b445b5640fd3312e080bd17efd3fbae4486b20ac00466308 \ + --hash=sha256:9a1b3ebc62d4bcdfdeba110944a25ab40916d5383c5e57e7c4a8dc0b6c17211a \ + --hash=sha256:9a52eea839e4bdc72c5e60a444d26004da00bb5bc6301e99b3dde18212e41465 \ + --hash=sha256:9c6d7fea39cb33e71de86397d38bf7ff1a6273e40367f31d05761662ffda49e4 \ + --hash=sha256:a53ca4d3f52f00b393fab9b5913c5bafb9afc27d030c8a1db1283da6917a860f \ + --hash=sha256:a7743cca45b4684c54407e8638f6d07b910d8d811347b9d42ff21262c7c23245 \ + --hash=sha256:aaf391fb6715866bc14681c76dc0308f46877f7c06f61d62cc993b79fc3c4a2a \ + --hash=sha256:ab9eab33ee3213f7751dc07a1a61b8d9a3d748ca4458fffddd9defa6f0493c16 \ + --hash=sha256:b04f29735bad9f06bb731c214f27253bd8bedb248ef9b8a1b4c5bde65b838454 \ + --hash=sha256:b1472986fd9c5d318399a01a0881f4a0bf4950264131bb8e2deba9df6d8c362b \ + --hash=sha256:b1d67d67f89e4e013a5295e7523bc34a7a96f2dba5dd812c7c8cb65d113cbf28 \ + --hash=sha256:b1f7efdd7b7adb32102c2fa481ad6f11923e2deb191f651274be559d56fc913b \ + --hash=sha256:b2669eafee38c5884a6e7cc9769d25c19428549dcdf57de8541cf9e82822e7db \ + --hash=sha256:ba26d87fe7fcb56c4a53b549a9e0e9143f6b0df56d35fe6ad800c902447acd5b \ + --hash=sha256:be15496e7244361ff0efcd86e52559bacda9cd975eccf19426a0025f9547c792 \ + --hash=sha256:c36539ed2c0173b053dafb221458812e178cfa3224ade0960599bec194637048 \ + --hash=sha256:c408f09649cbff8da76f8d3ad878b64ba7f7abdad1471efb293d2c075e80c822 \ + --hash=sha256:cd340bbd025302276b5aa221dccfe43040c7babfc32f107c36ad783f2ffd8775 \ + --hash=sha256:d0edecc3f90c2653298d380f6ea73b536944b767520c2179ec5d40b9145e47aa \ + --hash=sha256:d2a0f7e17f33e7890257367a1662b05fecaf56625f7dbb6446227aaa2b86448b \ + --hash=sha256:d71da0012face6f45432a11bc59af19e62fac5a41f8ce489e80c0add8153c3d1 \ + --hash=sha256:d895998fec712544c13cfe833890e0226585cf0391dd3948412441d5d68a2b8c \ + --hash=sha256:d95f9e9f3777b96241d8a00d6377cc9c716981d828b5091082d0fe3a2924b43e \ + --hash=sha256:d9727b85511b912571a76ce53c7640ba2c44c364e71cef6d7359b5412739c570 \ + --hash=sha256:d98a46cf07c0c875d27e8a7ed50f304d83063e49b9ab63f21c19c154b4c0d08d \ + --hash=sha256:d994cf27e2f874069884d9bddf0864f9b90ad201fcc9cb2f5b82bacc17c8d5f2 \ + --hash=sha256:dc0e0d41ad8a056a9886bac91ff9d9978e54a244deb61c2972cc76b66752de9c \ + --hash=sha256:dfaefe08af2a928e72344c800dcbaf6508e86a4ed481e28355e8d4b6a6a5230e \ + --hash=sha256:e60814edd0c9b511b5f377d48b9782b88cfe8be07a98f99973669299c8bb318a \ + --hash=sha256:eb8a54543d16ab1b69e2c5ed96cabbff16db044a50eddfc028000138ca9ddf33 \ + --hash=sha256:eb97c53112b593f89a90b4f6218635a9d1eea1d7f9521a3b7d24864228bbc0aa \ + --hash=sha256:ebadd5b8624d8ad503e505a99b8eb26fe3ea9f8e9c2234e805a27b269e585842 \ + --hash=sha256:ec8d7d8567e14af34a7911c98f5ac74a3d4a743cd848643341fc92b12b3784ff \ + --hash=sha256:ed78c8e94f57b44292c1a0350f580e18d3a3c5c0800e253f1583580c1b417ad2 \ + --hash=sha256:eea8d9e20632d68f653455265b18c35f90965e26f30d4d92f831899d6682149b \ + --hash=sha256:ef8937dae823b889c0273dfa0f0f6c46a3658ac0d851349c464d1b00e7ff4252 \ + --hash=sha256:f06e3c4c0a8badfc4910b9fd15beb1ad8f3b8fafa8ea82c023e5e607b66a78e4 \ + --hash=sha256:f0821b9bdf18c5b7d51722b906b233a39b17f602501a966cfbd9b285f8ab83cd \ + --hash=sha256:f0ba13557fec9d5ffc0a22826754a7457cc77f1b25145be10b7bb1d143ce84c6 \ + --hash=sha256:f382fec4a7891d66fb7163c90754454030bb9200a13f82ee7860b6359f3f2fa8 \ + --hash=sha256:fe7aaf5a54821d340d21412f7f6e6272a9b17a0cbafc1d68f77f2fc11009dcd5 \ + --hash=sha256:ff38378346b7018f42cbc1f6d1d3778e36e16d8595f79a312b31e7c25c50bd08 \ + --hash=sha256:ffa1bb0e26297b0f22881b219ffc82a33a3c84ce6174a9d69406239b14575bd5 # via -r src/backend/requirements.in -redis==5.2.1 +redis==5.2.1 \ + --hash=sha256:16f2e22dff21d5125e8481515e386711a34cbec50f0e44413dd7d9c060a54e0f \ + --hash=sha256:ee7e1056b9aea0f04c6c2ed59452947f34c4940ee025f5dd83e6a6418b6989e4 # via django-redis -referencing==0.36.2 +referencing==0.36.0 \ + --hash=sha256:01fc2916bab821aa3284d645bbbb41ba39609e7ff47072416a39ec2fb04d10d9 \ + --hash=sha256:246db964bb6101905167895cd66499cfb2aabc5f83277d008c52afe918ef29ba # via # jsonschema # jsonschema-specifications -requests==2.32.3 +requests==2.32.3 \ + --hash=sha256:55365417734eb18255590a9ff9eb97e9e1da868d4ccd6402399eaf68af20a760 \ + --hash=sha256:70761cfe03c773ceb22aa2f671b4757976145175cdfca038c02654d061d6dcc6 # via # coreapi # django-allauth # opentelemetry-exporter-otlp-proto-http # requests-oauthlib -requests-oauthlib==2.0.0 +requests-oauthlib==2.0.0 \ + --hash=sha256:7dd8a5c40426b779b0868c404bdef9768deccf22749cde15852df527e6269b36 \ + --hash=sha256:b3dffaebd884d8cd778494369603a9e7b58d29111bf6b41bdc2dcd87203af4e9 # via django-allauth -rpds-py==0.22.3 +rpds-py==0.22.3 \ + --hash=sha256:009de23c9c9ee54bf11303a966edf4d9087cd43a6003672e6aa7def643d06518 \ + --hash=sha256:02fbb9c288ae08bcb34fb41d516d5eeb0455ac35b5512d03181d755d80810059 \ + --hash=sha256:0a0461200769ab3b9ab7e513f6013b7a97fdeee41c29b9db343f3c5a8e2b9e61 \ + --hash=sha256:0b09865a9abc0ddff4e50b5ef65467cd94176bf1e0004184eb915cbc10fc05c5 \ + --hash=sha256:0b8db6b5b2d4491ad5b6bdc2bc7c017eec108acbf4e6785f42a9eb0ba234f4c9 \ + --hash=sha256:0c150c7a61ed4a4f4955a96626574e9baf1adf772c2fb61ef6a5027e52803543 \ + --hash=sha256:0f3cec041684de9a4684b1572fe28c7267410e02450f4561700ca5a3bc6695a2 \ + --hash=sha256:1352ae4f7c717ae8cba93421a63373e582d19d55d2ee2cbb184344c82d2ae55a \ + --hash=sha256:177c7c0fce2855833819c98e43c262007f42ce86651ffbb84f37883308cb0e7d \ + --hash=sha256:1978d0021e943aae58b9b0b196fb4895a25cc53d3956b8e35e0b7682eefb6d56 \ + --hash=sha256:1a60bce91f81ddaac922a40bbb571a12c1070cb20ebd6d49c48e0b101d87300d \ + --hash=sha256:1aef18820ef3e4587ebe8b3bc9ba6e55892a6d7b93bac6d29d9f631a3b4befbd \ + --hash=sha256:1e9663daaf7a63ceccbbb8e3808fe90415b0757e2abddbfc2e06c857bf8c5e2b \ + --hash=sha256:20070c65396f7373f5df4005862fa162db5d25d56150bddd0b3e8214e8ef45b4 \ + --hash=sha256:214b7a953d73b5e87f0ebece4a32a5bd83c60a3ecc9d4ec8f1dca968a2d91e99 \ + --hash=sha256:22bebe05a9ffc70ebfa127efbc429bc26ec9e9b4ee4d15a740033efda515cf3d \ + --hash=sha256:24e8abb5878e250f2eb0d7859a8e561846f98910326d06c0d51381fed59357bd \ + --hash=sha256:26fd7cac7dd51011a245f29a2cc6489c4608b5a8ce8d75661bb4a1066c52dfbe \ + --hash=sha256:27b1d3b3915a99208fee9ab092b8184c420f2905b7d7feb4aeb5e4a9c509b8a1 \ + --hash=sha256:27e98004595899949bd7a7b34e91fa7c44d7a97c40fcaf1d874168bb652ec67e \ + --hash=sha256:2b8f60e1b739a74bab7e01fcbe3dddd4657ec685caa04681df9d562ef15b625f \ + --hash=sha256:2de29005e11637e7a2361fa151f780ff8eb2543a0da1413bb951e9f14b699ef3 \ + --hash=sha256:2e8b55d8517a2fda8d95cb45d62a5a8bbf9dd0ad39c5b25c8833efea07b880ca \ + --hash=sha256:2fa4331c200c2521512595253f5bb70858b90f750d39b8cbfd67465f8d1b596d \ + --hash=sha256:3445e07bf2e8ecfeef6ef67ac83de670358abf2996916039b16a218e3d95e97e \ + --hash=sha256:3453e8d41fe5f17d1f8e9c383a7473cd46a63661628ec58e07777c2fff7196dc \ + --hash=sha256:378753b4a4de2a7b34063d6f95ae81bfa7b15f2c1a04a9518e8644e81807ebea \ + --hash=sha256:3af6e48651c4e0d2d166dc1b033b7042ea3f871504b6805ba5f4fe31581d8d38 \ + --hash=sha256:3dfcbc95bd7992b16f3f7ba05af8a64ca694331bd24f9157b49dadeeb287493b \ + --hash=sha256:3f21f0495edea7fdbaaa87e633a8689cd285f8f4af5c869f27bc8074638ad69c \ + --hash=sha256:4041711832360a9b75cfb11b25a6a97c8fb49c07b8bd43d0d02b45d0b499a4ff \ + --hash=sha256:44d61b4b7d0c2c9ac019c314e52d7cbda0ae31078aabd0f22e583af3e0d79723 \ + --hash=sha256:4617e1915a539a0d9a9567795023de41a87106522ff83fbfaf1f6baf8e85437e \ + --hash=sha256:4b232061ca880db21fa14defe219840ad9b74b6158adb52ddf0e87bead9e8493 \ + --hash=sha256:5246b14ca64a8675e0a7161f7af68fe3e910e6b90542b4bfb5439ba752191df6 \ + --hash=sha256:5725dd9cc02068996d4438d397e255dcb1df776b7ceea3b9cb972bdb11260a83 \ + --hash=sha256:583f6a1993ca3369e0f80ba99d796d8e6b1a3a2a442dd4e1a79e652116413091 \ + --hash=sha256:59259dc58e57b10e7e18ce02c311804c10c5a793e6568f8af4dead03264584d1 \ + --hash=sha256:593eba61ba0c3baae5bc9be2f5232430453fb4432048de28399ca7376de9c627 \ + --hash=sha256:59f4a79c19232a5774aee369a0c296712ad0e77f24e62cad53160312b1c1eaa1 \ + --hash=sha256:5f0e260eaf54380380ac3808aa4ebe2d8ca28b9087cf411649f96bad6900c728 \ + --hash=sha256:62d9cfcf4948683a18a9aff0ab7e1474d407b7bab2ca03116109f8464698ab16 \ + --hash=sha256:64607d4cbf1b7e3c3c8a14948b99345eda0e161b852e122c6bb71aab6d1d798c \ + --hash=sha256:655ca44a831ecb238d124e0402d98f6212ac527a0ba6c55ca26f616604e60a45 \ + --hash=sha256:666ecce376999bf619756a24ce15bb14c5bfaf04bf00abc7e663ce17c3f34fe7 \ + --hash=sha256:68049202f67380ff9aa52f12e92b1c30115f32e6895cd7198fa2a7961621fc5a \ + --hash=sha256:69803198097467ee7282750acb507fba35ca22cc3b85f16cf45fb01cb9097730 \ + --hash=sha256:6c7b99ca52c2c1752b544e310101b98a659b720b21db00e65edca34483259967 \ + --hash=sha256:6dd9412824c4ce1aca56c47b0991e65bebb7ac3f4edccfd3f156150c96a7bf25 \ + --hash=sha256:70eb60b3ae9245ddea20f8a4190bd79c705a22f8028aaf8bbdebe4716c3fab24 \ + --hash=sha256:70fb28128acbfd264eda9bf47015537ba3fe86e40d046eb2963d75024be4d055 \ + --hash=sha256:7b2513ba235829860b13faa931f3b6846548021846ac808455301c23a101689d \ + --hash=sha256:7ef9d9da710be50ff6809fed8f1963fecdfecc8b86656cadfca3bc24289414b0 \ + --hash=sha256:81e69b0a0e2537f26d73b4e43ad7bc8c8efb39621639b4434b76a3de50c6966e \ + --hash=sha256:8633e471c6207a039eff6aa116e35f69f3156b3989ea3e2d755f7bc41754a4a7 \ + --hash=sha256:8bd7c8cfc0b8247c8799080fbff54e0b9619e17cdfeb0478ba7295d43f635d7c \ + --hash=sha256:9253fc214112405f0afa7db88739294295f0e08466987f1d70e29930262b4c8f \ + --hash=sha256:99b37292234e61325e7a5bb9689e55e48c3f5f603af88b1642666277a81f1fbd \ + --hash=sha256:9bd7228827ec7bb817089e2eb301d907c0d9827a9e558f22f762bb690b131652 \ + --hash=sha256:9beeb01d8c190d7581a4d59522cd3d4b6887040dcfc744af99aa59fef3e041a8 \ + --hash=sha256:a63cbdd98acef6570c62b92a1e43266f9e8b21e699c363c0fef13bd530799c11 \ + --hash=sha256:a76e42402542b1fae59798fab64432b2d015ab9d0c8c47ba7addddbaf7952333 \ + --hash=sha256:ac0a03221cdb5058ce0167ecc92a8c89e8d0decdc9e99a2ec23380793c4dcb96 \ + --hash=sha256:b0b4136a252cadfa1adb705bb81524eee47d9f6aab4f2ee4fa1e9d3cd4581f64 \ + --hash=sha256:b25bc607423935079e05619d7de556c91fb6adeae9d5f80868dde3468657994b \ + --hash=sha256:b3d504047aba448d70cf6fa22e06cb09f7cbd761939fdd47604f5e007675c24e \ + --hash=sha256:bb47271f60660803ad11f4c61b42242b8c1312a31c98c578f79ef9387bbde21c \ + --hash=sha256:bbb232860e3d03d544bc03ac57855cd82ddf19c7a07651a7c0fdb95e9efea8b9 \ + --hash=sha256:bc27863442d388870c1809a87507727b799c8460573cfbb6dc0eeaef5a11b5ec \ + --hash=sha256:bc51abd01f08117283c5ebf64844a35144a0843ff7b2983e0648e4d3d9f10dbb \ + --hash=sha256:be2eb3f2495ba669d2a985f9b426c1797b7d48d6963899276d22f23e33d47e37 \ + --hash=sha256:bf9db5488121b596dbfc6718c76092fda77b703c1f7533a226a5a9f65248f8ad \ + --hash=sha256:c58e2339def52ef6b71b8f36d13c3688ea23fa093353f3a4fee2556e62086ec9 \ + --hash=sha256:cfbc454a2880389dbb9b5b398e50d439e2e58669160f27b60e5eca11f68ae17c \ + --hash=sha256:cff63a0272fcd259dcc3be1657b07c929c466b067ceb1c20060e8d10af56f5bf \ + --hash=sha256:d115bffdd417c6d806ea9069237a4ae02f513b778e3789a359bc5856e0404cc4 \ + --hash=sha256:d20cfb4e099748ea39e6f7b16c91ab057989712d31761d3300d43134e26e165f \ + --hash=sha256:d48424e39c2611ee1b84ad0f44fb3b2b53d473e65de061e3f460fc0be5f1939d \ + --hash=sha256:e0fa2d4ec53dc51cf7d3bb22e0aa0143966119f42a0c3e4998293a3dd2856b09 \ + --hash=sha256:e32fee8ab45d3c2db6da19a5323bc3362237c8b653c70194414b892fd06a080d \ + --hash=sha256:e35ba67d65d49080e8e5a1dd40101fccdd9798adb9b050ff670b7d74fa41c566 \ + --hash=sha256:e3fb866d9932a3d7d0c82da76d816996d1667c44891bd861a0f97ba27e84fc74 \ + --hash=sha256:e61b02c3f7a1e0b75e20c3978f7135fd13cb6cf551bf4a6d29b999a88830a338 \ + --hash=sha256:e67ba3c290821343c192f7eae1d8fd5999ca2dc99994114643e2f2d3e6138b15 \ + --hash=sha256:e79dd39f1e8c3504be0607e5fc6e86bb60fe3584bec8b782578c3b0fde8d932c \ + --hash=sha256:e89391e6d60251560f0a8f4bd32137b077a80d9b7dbe6d5cab1cd80d2746f648 \ + --hash=sha256:ea7433ce7e4bfc3a85654aeb6747babe3f66eaf9a1d0c1e7a4435bbdf27fea84 \ + --hash=sha256:eaf16ae9ae519a0e237a0f528fd9f0197b9bb70f40263ee57ae53c2b8d48aeb3 \ + --hash=sha256:eb0c341fa71df5a4595f9501df4ac5abfb5a09580081dffbd1ddd4654e6e9123 \ + --hash=sha256:f276b245347e6e36526cbd4a266a417796fc531ddf391e43574cf6466c492520 \ + --hash=sha256:f47ad3d5f3258bd7058d2d506852217865afefe6153a36eb4b6928758041d831 \ + --hash=sha256:f56a6b404f74ab372da986d240e2e002769a7d7102cc73eb238a4f72eec5284e \ + --hash=sha256:f5cf2a0c2bdadf3791b5c205d55a37a54025c6e18a71c71f82bb536cf9a454bf \ + --hash=sha256:f5d36399a1b96e1a5fdc91e0522544580dbebeb1f77f27b2b0ab25559e103b8b \ + --hash=sha256:f60bd8423be1d9d833f230fdbccf8f57af322d96bcad6599e5a771b151398eb2 \ + --hash=sha256:f612463ac081803f243ff13cccc648578e2279295048f2a8d5eb430af2bae6e3 \ + --hash=sha256:f73d3fef726b3243a811121de45193c0ca75f6407fe66f3f4e183c983573e130 \ + --hash=sha256:f82a116a1d03628a8ace4859556fb39fd1424c933341a08ea3ed6de1edb0283b \ + --hash=sha256:fb0ba113b4983beac1a2eb16faffd76cb41e176bf58c4afe3e14b9c681f702de \ + --hash=sha256:fb4f868f712b2dd4bcc538b0a0c1f63a2b1d584c925e69a224d759e7070a12d5 \ + --hash=sha256:fb6116dfb8d1925cbdb52595560584db42a7f664617a1f7d7f6e32f138cdf37d \ + --hash=sha256:fda7cb070f442bf80b642cd56483b5548e43d366fe3f39b98e67cce780cded00 \ + --hash=sha256:feea821ee2a9273771bae61194004ee2fc33f8ec7db08117ef9147d4bbcbca8e # via # jsonschema # referencing -sentry-sdk==2.20.0 +sentry-sdk==2.20.0 \ + --hash=sha256:afa82713a92facf847df3c6f63cec71eb488d826a50965def3d7722aa6f0fdab \ + --hash=sha256:c359a1edf950eb5e80cffd7d9111f3dbeef57994cb4415df37d39fda2cf22364 # via # -r src/backend/requirements.in # django-q-sentry -setuptools==75.8.0 +setuptools==75.8.0 \ + --hash=sha256:c5afc8f407c626b8313a86e10311dd3f661c6cd9c09d4bf8c15c0e11f9f2b0e6 \ + --hash=sha256:e3982f444617239225d675215d51f6ba05f845d4eec313da4418fdbb56fb27e3 # via # -r src/backend/requirements.in # django-money -sgmllib3k==1.0.0 +sgmllib3k==1.0.0 \ + --hash=sha256:7868fb1c8bfa764c1ac563d3cf369c381d1325d36124933a726f29fcdaa812e9 # via feedparser -six==1.17.0 +six==1.17.0 \ + --hash=sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274 \ + --hash=sha256:ff70335d468e7eb6ec65b95b99d3a2836546063f63acc5171de367e834932a81 # via # html5lib # python-dateutil -sqlparse==0.5.3 +sqlparse==0.5.3 \ + --hash=sha256:09f67787f56a0b16ecdbde1bfc7f5d9c3371ca683cfeaa8e6ff60b4807ec9272 \ + --hash=sha256:cf2196ed3418f3ba5de6af7e82c694a9fbdbfecccdfc72e281548517081f16ca # via # django # django-sql-utils -structlog==25.1.0 +structlog==25.1.0 \ + --hash=sha256:2ef2a572e0e27f09664965d31a576afe64e46ac6084ef5cec3c2b8cd6e4e3ad3 \ + --hash=sha256:843fe4f254540329f380812cbe612e1af5ec5b8172205ae634679cd35a6d6321 # via django-structlog -tablib[xls, xlsx, yaml]==3.8.0 +tablib[xls, xlsx, yaml]==3.7.0 \ + --hash=sha256:9a6930037cfe0f782377963ca3f2b1dae3fd4cdbf0883848f22f1447e7bb718b \ + --hash=sha256:f9db84ed398df5109bd69c11d46613d16cc572fb9ad3213f10d95e2b5f12c18e # via -r src/backend/requirements.in -tinycss2==1.4.0 +tinycss2==1.4.0 \ + --hash=sha256:10c0972f6fc0fbee87c3edb76549357415e94548c1ae10ebccdea16fb404a9b7 \ + --hash=sha256:3a49cf47b7675da0b15d0c6e1df8df4ebd96e9394bb905a5775adb0d884c5289 # via # bleach # cssselect2 # weasyprint -tomli==2.2.1 +tomli==2.2.1 \ + --hash=sha256:023aa114dd824ade0100497eb2318602af309e5a55595f76b626d6d9f3b7b0a6 \ + --hash=sha256:02abe224de6ae62c19f090f68da4e27b10af2b93213d36cf44e6e1c5abd19fdd \ + --hash=sha256:286f0ca2ffeeb5b9bd4fcc8d6c330534323ec51b2f52da063b11c502da16f30c \ + --hash=sha256:2d0f2fdd22b02c6d81637a3c95f8cd77f995846af7414c5c4b8d0545afa1bc4b \ + --hash=sha256:33580bccab0338d00994d7f16f4c4ec25b776af3ffaac1ed74e0b3fc95e885a8 \ + --hash=sha256:400e720fe168c0f8521520190686ef8ef033fb19fc493da09779e592861b78c6 \ + --hash=sha256:40741994320b232529c802f8bc86da4e1aa9f413db394617b9a256ae0f9a7f77 \ + --hash=sha256:465af0e0875402f1d226519c9904f37254b3045fc5084697cefb9bdde1ff99ff \ + --hash=sha256:4a8f6e44de52d5e6c657c9fe83b562f5f4256d8ebbfe4ff922c495620a7f6cea \ + --hash=sha256:4e340144ad7ae1533cb897d406382b4b6fede8890a03738ff1683af800d54192 \ + --hash=sha256:678e4fa69e4575eb77d103de3df8a895e1591b48e740211bd1067378c69e8249 \ + --hash=sha256:6972ca9c9cc9f0acaa56a8ca1ff51e7af152a9f87fb64623e31d5c83700080ee \ + --hash=sha256:7fc04e92e1d624a4a63c76474610238576942d6b8950a2d7f908a340494e67e4 \ + --hash=sha256:889f80ef92701b9dbb224e49ec87c645ce5df3fa2cc548664eb8a25e03127a98 \ + --hash=sha256:8d57ca8095a641b8237d5b079147646153d22552f1c637fd3ba7f4b0b29167a8 \ + --hash=sha256:8dd28b3e155b80f4d54beb40a441d366adcfe740969820caf156c019fb5c7ec4 \ + --hash=sha256:9316dc65bed1684c9a98ee68759ceaed29d229e985297003e494aa825ebb0281 \ + --hash=sha256:a198f10c4d1b1375d7687bc25294306e551bf1abfa4eace6650070a5c1ae2744 \ + --hash=sha256:a38aa0308e754b0e3c67e344754dff64999ff9b513e691d0e786265c93583c69 \ + --hash=sha256:a92ef1a44547e894e2a17d24e7557a5e85a9e1d0048b0b5e7541f76c5032cb13 \ + --hash=sha256:ac065718db92ca818f8d6141b5f66369833d4a80a9d74435a268c52bdfa73140 \ + --hash=sha256:b82ebccc8c8a36f2094e969560a1b836758481f3dc360ce9a3277c65f374285e \ + --hash=sha256:c954d2250168d28797dd4e3ac5cf812a406cd5a92674ee4c8f123c889786aa8e \ + --hash=sha256:cb55c73c5f4408779d0cf3eef9f762b9c9f147a77de7b258bef0a5628adc85cc \ + --hash=sha256:cd45e1dc79c835ce60f7404ec8119f2eb06d38b1deba146f07ced3bbc44505ff \ + --hash=sha256:d3f5614314d758649ab2ab3a62d4f2004c825922f9e370b29416484086b264ec \ + --hash=sha256:d920f33822747519673ee656a4b6ac33e382eca9d331c87770faa3eef562aeb2 \ + --hash=sha256:db2b95f9de79181805df90bedc5a5ab4c165e6ec3fe99f970d0e302f384ad222 \ + --hash=sha256:e59e304978767a54663af13c07b3d1af22ddee3bb2fb0618ca1593e4f593a106 \ + --hash=sha256:e85e99945e688e32d5a35c1ff38ed0b3f41f43fad8df0bdf79f72b2ba7bc5272 \ + --hash=sha256:ece47d672db52ac607a3d9599a9d48dcb2f2f735c6c2d1f34130085bb12b112a \ + --hash=sha256:f4039b9cbc3048b2416cc57ab3bda989a6fcf9b36cf8937f01a6e731b64f80d7 # via pip-licenses -typing-extensions==4.12.2 +typing-extensions==4.12.2 \ + --hash=sha256:04e5ca0351e0f3f85c6853954072df659d0d13fac324d0072316b67d7794700d \ + --hash=sha256:1a7ead55c7e559dd4dee8856e3a88b41225abfe1ce8df57b7c13915fe121ffb8 # via # asgiref # drf-spectacular @@ -393,43 +1627,271 @@ typing-extensions==4.12.2 # py-moneyed # referencing # structlog -tzdata==2025.1 +tzdata==2024.2 \ + --hash=sha256:7d85cc416e9382e69095b7bdf4afd9e3880418a2413feec7069d533d6b4e31cc \ + --hash=sha256:a48093786cdcde33cad18c2555e8532f34422074448fbc874186f0abd79565cd # via icalendar -uritemplate==4.1.1 +uritemplate==4.1.1 \ + --hash=sha256:4346edfc5c3b79f694bccd6d6099a322bbeb628dbf2cd86eea55a456ce5124f0 \ + --hash=sha256:830c08b8d99bdd312ea4ead05994a38e8936266f84b9a7878232db50b044e02e # via # coreapi # drf-spectacular -urllib3==2.3.0 +urllib3==2.3.0 \ + --hash=sha256:1cee9ad369867bfdbbb48b7dd50374c0967a0bb7710050facf0dd6911440e3df \ + --hash=sha256:f8c5449b3cf0861679ce7e0503c7b44b5ec981bec0d1d3795a07f1ba96f0204d # via # dulwich # requests # sentry-sdk -wcwidth==0.2.13 +wcwidth==0.2.13 \ + --hash=sha256:3da69048e4540d84af32131829ff948f1e022c1c6bdb8d6102117aac784f6859 \ + --hash=sha256:72ea0c06399eb286d978fdedb6923a9eb47e1c486ce63e9b4e64fc18303972b5 # via prettytable -weasyprint==62.3 +weasyprint==62.3 \ + --hash=sha256:8d8680d732f7fa0fcbc587692a5a5cb095c3525627066918d6e203cbf42b7fcd \ + --hash=sha256:d31048646ce15084e135b33e334a61f526aa68d2f679fcc109ed0e0f5edaed21 # via # -r src/backend/requirements.in # django-weasyprint -webencodings==0.5.1 +webencodings==0.5.1 \ + --hash=sha256:a0af1213f3c2226497a97e2b3aa01a7e4bee4f403f95be16fc9acd2947514a78 \ + --hash=sha256:b36a1c245f2d304965eb4e0a82848379241dc04b865afcc4aab16748587e1923 # via # bleach # cssselect2 # html5lib # tinycss2 -whitenoise==6.8.2 +whitenoise==6.8.2 \ + --hash=sha256:486bd7267a375fa9650b136daaec156ac572971acc8bf99add90817a530dd1d4 \ + --hash=sha256:df12dce147a043d1956d81d288c6f0044147c6d2ab9726e5772ac50fb45d2280 # via -r src/backend/requirements.in -wrapt==1.17.2 +wrapt==1.17.2 \ + --hash=sha256:08e7ce672e35efa54c5024936e559469436f8b8096253404faeb54d2a878416f \ + --hash=sha256:0a6e821770cf99cc586d33833b2ff32faebdbe886bd6322395606cf55153246c \ + --hash=sha256:0b929ac182f5ace000d459c59c2c9c33047e20e935f8e39371fa6e3b85d56f4a \ + --hash=sha256:129a150f5c445165ff941fc02ee27df65940fcb8a22a61828b1853c98763a64b \ + --hash=sha256:13e6afb7fe71fe7485a4550a8844cc9ffbe263c0f1a1eea569bc7091d4898555 \ + --hash=sha256:1473400e5b2733e58b396a04eb7f35f541e1fb976d0c0724d0223dd607e0f74c \ + --hash=sha256:18983c537e04d11cf027fbb60a1e8dfd5190e2b60cc27bc0808e653e7b218d1b \ + --hash=sha256:1a7ed2d9d039bd41e889f6fb9364554052ca21ce823580f6a07c4ec245c1f5d6 \ + --hash=sha256:1e1fe0e6ab7775fd842bc39e86f6dcfc4507ab0ffe206093e76d61cde37225c8 \ + --hash=sha256:1fb5699e4464afe5c7e65fa51d4f99e0b2eadcc176e4aa33600a3df7801d6662 \ + --hash=sha256:2696993ee1eebd20b8e4ee4356483c4cb696066ddc24bd70bcbb80fa56ff9061 \ + --hash=sha256:35621ae4c00e056adb0009f8e86e28eb4a41a4bfa8f9bfa9fca7d343fe94f998 \ + --hash=sha256:36ccae62f64235cf8ddb682073a60519426fdd4725524ae38874adf72b5f2aeb \ + --hash=sha256:3cedbfa9c940fdad3e6e941db7138e26ce8aad38ab5fe9dcfadfed9db7a54e62 \ + --hash=sha256:3d57c572081fed831ad2d26fd430d565b76aa277ed1d30ff4d40670b1c0dd984 \ + --hash=sha256:3fc7cb4c1c744f8c05cd5f9438a3caa6ab94ce8344e952d7c45a8ed59dd88392 \ + --hash=sha256:4011d137b9955791f9084749cba9a367c68d50ab8d11d64c50ba1688c9b457f2 \ + --hash=sha256:40d615e4fe22f4ad3528448c193b218e077656ca9ccb22ce2cb20db730f8d306 \ + --hash=sha256:410a92fefd2e0e10d26210e1dfb4a876ddaf8439ef60d6434f21ef8d87efc5b7 \ + --hash=sha256:41388e9d4d1522446fe79d3213196bd9e3b301a336965b9e27ca2788ebd122f3 \ + --hash=sha256:468090021f391fe0056ad3e807e3d9034e0fd01adcd3bdfba977b6fdf4213ea9 \ + --hash=sha256:49703ce2ddc220df165bd2962f8e03b84c89fee2d65e1c24a7defff6f988f4d6 \ + --hash=sha256:4a721d3c943dae44f8e243b380cb645a709ba5bd35d3ad27bc2ed947e9c68192 \ + --hash=sha256:4afd5814270fdf6380616b321fd31435a462019d834f83c8611a0ce7484c7317 \ + --hash=sha256:4c82b8785d98cdd9fed4cac84d765d234ed3251bd6afe34cb7ac523cb93e8b4f \ + --hash=sha256:4db983e7bca53819efdbd64590ee96c9213894272c776966ca6306b73e4affda \ + --hash=sha256:582530701bff1dec6779efa00c516496968edd851fba224fbd86e46cc6b73563 \ + --hash=sha256:58455b79ec2661c3600e65c0a716955adc2410f7383755d537584b0de41b1d8a \ + --hash=sha256:58705da316756681ad3c9c73fd15499aa4d8c69f9fd38dc8a35e06c12468582f \ + --hash=sha256:5bb1d0dbf99411f3d871deb6faa9aabb9d4e744d67dcaaa05399af89d847a91d \ + --hash=sha256:5c803c401ea1c1c18de70a06a6f79fcc9c5acfc79133e9869e730ad7f8ad8ef9 \ + --hash=sha256:5cbabee4f083b6b4cd282f5b817a867cf0b1028c54d445b7ec7cfe6505057cf8 \ + --hash=sha256:612dff5db80beef9e649c6d803a8d50c409082f1fedc9dbcdfde2983b2025b82 \ + --hash=sha256:62c2caa1585c82b3f7a7ab56afef7b3602021d6da34fbc1cf234ff139fed3cd9 \ + --hash=sha256:69606d7bb691b50a4240ce6b22ebb319c1cfb164e5f6569835058196e0f3a845 \ + --hash=sha256:6d9187b01bebc3875bac9b087948a2bccefe464a7d8f627cf6e48b1bbae30f82 \ + --hash=sha256:6ed6ffac43aecfe6d86ec5b74b06a5be33d5bb9243d055141e8cabb12aa08125 \ + --hash=sha256:703919b1633412ab54bcf920ab388735832fdcb9f9a00ae49387f0fe67dad504 \ + --hash=sha256:766d8bbefcb9e00c3ac3b000d9acc51f1b399513f44d77dfe0eb026ad7c9a19b \ + --hash=sha256:80dd7db6a7cb57ffbc279c4394246414ec99537ae81ffd702443335a61dbf3a7 \ + --hash=sha256:8112e52c5822fc4253f3901b676c55ddf288614dc7011634e2719718eaa187dc \ + --hash=sha256:8c8b293cd65ad716d13d8dd3624e42e5a19cc2a2f1acc74b30c2c13f15cb61a6 \ + --hash=sha256:8fdbdb757d5390f7c675e558fd3186d590973244fab0c5fe63d373ade3e99d40 \ + --hash=sha256:91bd7d1773e64019f9288b7a5101f3ae50d3d8e6b1de7edee9c2ccc1d32f0c0a \ + --hash=sha256:95c658736ec15602da0ed73f312d410117723914a5c91a14ee4cdd72f1d790b3 \ + --hash=sha256:99039fa9e6306880572915728d7f6c24a86ec57b0a83f6b2491e1d8ab0235b9a \ + --hash=sha256:9a2bce789a5ea90e51a02dfcc39e31b7f1e662bc3317979aa7e5538e3a034f72 \ + --hash=sha256:9a7d15bbd2bc99e92e39f49a04653062ee6085c0e18b3b7512a4f2fe91f2d681 \ + --hash=sha256:9abc77a4ce4c6f2a3168ff34b1da9b0f311a8f1cfd694ec96b0603dff1c79438 \ + --hash=sha256:9e8659775f1adf02eb1e6f109751268e493c73716ca5761f8acb695e52a756ae \ + --hash=sha256:9fee687dce376205d9a494e9c121e27183b2a3df18037f89d69bd7b35bcf59e2 \ + --hash=sha256:a5aaeff38654462bc4b09023918b7f21790efb807f54c000a39d41d69cf552cb \ + --hash=sha256:a604bf7a053f8362d27eb9fefd2097f82600b856d5abe996d623babd067b1ab5 \ + --hash=sha256:abbb9e76177c35d4e8568e58650aa6926040d6a9f6f03435b7a522bf1c487f9a \ + --hash=sha256:acc130bc0375999da18e3d19e5a86403667ac0c4042a094fefb7eec8ebac7cf3 \ + --hash=sha256:b18f2d1533a71f069c7f82d524a52599053d4c7166e9dd374ae2136b7f40f7c8 \ + --hash=sha256:b4e42a40a5e164cbfdb7b386c966a588b1047558a990981ace551ed7e12ca9c2 \ + --hash=sha256:b5e251054542ae57ac7f3fba5d10bfff615b6c2fb09abeb37d2f1463f841ae22 \ + --hash=sha256:b60fb58b90c6d63779cb0c0c54eeb38941bae3ecf7a73c764c52c88c2dcb9d72 \ + --hash=sha256:b870b5df5b71d8c3359d21be8f0d6c485fa0ebdb6477dda51a1ea54a9b558061 \ + --hash=sha256:ba0f0eb61ef00ea10e00eb53a9129501f52385c44853dbd6c4ad3f403603083f \ + --hash=sha256:bb87745b2e6dc56361bfde481d5a378dc314b252a98d7dd19a651a3fa58f24a9 \ + --hash=sha256:bb90fb8bda722a1b9d48ac1e6c38f923ea757b3baf8ebd0c82e09c5c1a0e7a04 \ + --hash=sha256:bc570b5f14a79734437cb7b0500376b6b791153314986074486e0b0fa8d71d98 \ + --hash=sha256:c86563182421896d73858e08e1db93afdd2b947a70064b813d515d66549e15f9 \ + --hash=sha256:c958bcfd59bacc2d0249dcfe575e71da54f9dcf4a8bdf89c4cb9a68a1170d73f \ + --hash=sha256:d18a4865f46b8579d44e4fe1e2bcbc6472ad83d98e22a26c963d46e4c125ef0b \ + --hash=sha256:d5e2439eecc762cd85e7bd37161d4714aa03a33c5ba884e26c81559817ca0925 \ + --hash=sha256:e3890b508a23299083e065f435a492b5435eba6e304a7114d2f919d400888cc6 \ + --hash=sha256:e496a8ce2c256da1eb98bd15803a79bee00fc351f5dfb9ea82594a3f058309e0 \ + --hash=sha256:e8b2816ebef96d83657b56306152a93909a83f23994f4b30ad4573b00bd11bb9 \ + --hash=sha256:eaf675418ed6b3b31c7a989fd007fa7c3be66ce14e5c3b27336383604c9da85c \ + --hash=sha256:ec89ed91f2fa8e3f52ae53cd3cf640d6feff92ba90d62236a81e4e563ac0e991 \ + --hash=sha256:ecc840861360ba9d176d413a5489b9a0aff6d6303d7e733e2c4623cfa26904a6 \ + --hash=sha256:f09b286faeff3c750a879d336fb6d8713206fc97af3adc14def0cdd349df6000 \ + --hash=sha256:f393cda562f79828f38a819f4788641ac7c4085f30f1ce1a68672baa686482bb \ + --hash=sha256:f917c1180fdb8623c2b75a99192f4025e412597c50b2ac870f156de8fb101119 \ + --hash=sha256:fc78a84e2dfbc27afe4b2bd7c80c8db9bca75cc5b85df52bfe634596a1da846b \ + --hash=sha256:ff04ef6eec3eee8a5efef2401495967a916feaa353643defcc03fc74fe213b58 # via # deprecated # opentelemetry-instrumentation # opentelemetry-instrumentation-redis -xlrd==2.0.1 +xlrd==2.0.1 \ + --hash=sha256:6a33ee89877bd9abc1158129f6e94be74e2679636b8a205b43b85206c3f0bbdd \ + --hash=sha256:f72f148f54442c6b056bf931dbc34f986fd0c3b0b6b5a58d013c9aef274d0c88 # via tablib -xlwt==1.3.0 +xlwt==1.3.0 \ + --hash=sha256:a082260524678ba48a297d922cc385f58278b8aa68741596a87de01a9c628b2e \ + --hash=sha256:c59912717a9b28f1a3c2a98fd60741014b06b043936dcecbc113eaaada156c88 # via tablib -xmlsec==1.3.14 +xmlsec==1.3.14 \ + --hash=sha256:004e8a82e26728bf8a60f8ece1ef3ffafdac30ef538139dfe28870e8503ca64a \ + --hash=sha256:03ccba7dacf197850de954666af0221c740a5de631a80136362a1559223fab75 \ + --hash=sha256:0bae37b2920115cf00759ee9fb7841cbdebcef3a8a92734ab93ae8fa41ac581d \ + --hash=sha256:0be3b7a28e54a03b87faf07fb3c6dc3e50a2c79b686718c3ad08300b8bf6bb67 \ + --hash=sha256:1072878301cb9243a54679e0520e6a5be2266c07a28b0ecef9e029d05a90ffcd \ + --hash=sha256:12d90059308bb0c1b94bde065784e6852999d08b91bcb2048c17e62b954acb07 \ + --hash=sha256:147934bd39dfd840663fb6b920ea9201455fa886427975713f1b42d9f20b9b29 \ + --hash=sha256:19c86bab1498e4c2e56d8e2c878f461ccb6e56b67fd7522b0c8fda46d8910781 \ + --hash=sha256:1b9b5de6bc69fdec23147e5f712cb05dc86df105462f254f140d743cc680cc7b \ + --hash=sha256:1eb3dcf244a52f796377112d8f238dbb522eb87facffb498425dc8582a84a6bf \ + --hash=sha256:1fa1311f7489d050dde9028f5a2b5849c2927bb09c9a93491cb2f28fdc563912 \ + --hash=sha256:1fe23c2dd5f5dbcb24f40e2c1061e2672a32aabee7cf8ac5337036a485607d72 \ + --hash=sha256:204d3c586b8bd6f02a5d4c59850a8157205569d40c32567f49576fa5795d897d \ + --hash=sha256:2401e162aaab7d9416c3405bac7a270e5f370988a0f1f46f0f29b735edba87e1 \ + --hash=sha256:28cd9f513cf01dc0c5b9d9f0728714ecde2e7f46b3b6f63de91f4ae32f3008b3 \ + --hash=sha256:2f84a1c509c52773365645a87949081ee9ea9c535cd452048cc8ca4ad3b45666 \ + --hash=sha256:330147ce59fbe56a9be5b2085d739c55a569f112576b3f1b33681f87416eaf33 \ + --hash=sha256:34c61ec0c0e70fda710290ae74b9efe1928d9242ed82c4eecf97aa696cff68e6 \ + --hash=sha256:38e035bf48300b7dbde2dd01d3b8569f8584fc9c73809be13886e6b6c77b74fb \ + --hash=sha256:48e894ad3e7de373f56efc09d6a56f7eae73a8dd4cec8943313134849e9c6607 \ + --hash=sha256:4922afa9234d1c5763950b26c328a5320019e55eb6000272a79dfe54fee8e704 \ + --hash=sha256:4af81ce8044862ec865782efd353d22abdcd95b92364eef3c934de57ae6d5852 \ + --hash=sha256:4dea6df3ffcb65d0b215678c3a0fe7bbc66785d6eae81291296e372498bad43a \ + --hash=sha256:4edd8db4df04bbac9c4a5ab4af855b74fe2bf2c248d07cac2e6d92a485f1a685 \ + --hash=sha256:4fac2a787ae3b9fb761f9aec6b9f10f2d1c1b87abb574ebd8ff68435bdc97e3d \ + --hash=sha256:57fed3bc7943681c9ed4d2221600ab440f060d8d1a8f92f346f2b41effe175b8 \ + --hash=sha256:6566434e2e5c58e472362a6187f208601f1627a148683a6f92bd16479f1d9e20 \ + --hash=sha256:6679cec780386d848e7351d4b0de92c4483289ea4f0a2187e216159f939a4c6b \ + --hash=sha256:73eabf5ef58189d81655058cf328c1dfa9893d89f1bff5fc941481f08533f338 \ + --hash=sha256:774d5d1e45f07f953c1cc14fd055c1063f0725f7248b6b0e681f59fd8638934d \ + --hash=sha256:77749b338503fb6e151052c664064b34264f4168e2cb0cca1de78b7e5312a783 \ + --hash=sha256:7799a9ff3593f9dd43464e18b1a621640bffc40456c47c23383727f937dca7fc \ + --hash=sha256:7882963e9cb9c0bd0e8c2715a29159a366417ff4a30d8baf42b05bc5cf249446 \ + --hash=sha256:7e8e0171916026cbe8e2022c959558d02086655fd3c3466f2bc0451b09cf9ee8 \ + --hash=sha256:82ac81deb7d7bf5cc8a748148948e5df5386597ff43fb92ec651cc5c7addb0e7 \ + --hash=sha256:86ff7b2711557c1087b72b0a1a88d82eafbf2a6d38b97309a6f7101d4a7041c3 \ + --hash=sha256:934f804f2f895bcdb86f1eaee236b661013560ee69ec108d29cdd6e5f292a2d9 \ + --hash=sha256:995e87acecc263a2f6f2aa3cc204268f651cac8f4d7a2047f11b2cd49979cc38 \ + --hash=sha256:a487c3d144f791c32f5e560aa27a705fba23171728b8a8511f36de053ff6bc93 \ + --hash=sha256:a98eadfcb0c3b23ccceb7a2f245811f8d784bd287640dcfe696a26b9db1e2fc0 \ + --hash=sha256:ad1634cabe0915fe2a12e142db0ed2daf5be80cbe3891a2cecbba0750195cc6b \ + --hash=sha256:b109cdf717257fd4daa77c1d3ec8a3fb2a81318a6d06a36c55a8a53ae381ae5e \ + --hash=sha256:b6dd86f440fec9242515c64f0be93fec8b4289287db1f6de2651eee9995aaecb \ + --hash=sha256:b7ba2ea38e3d9efa520b14f3c0b7d99a7c055244ae5ba8bc9f4ca73b18f3a215 \ + --hash=sha256:ba3b39c493e3b04354615068a3218f30897fcc2f42c6d8986d0c1d63aca87782 \ + --hash=sha256:bd10ca3201f164482775a7ce61bf7ee9aade2e7d032046044dd0f6f52c91d79d \ + --hash=sha256:bddd2a2328b4e08c8a112e06cf2cd2b4d281f4ad94df15b4cef18f06cdc49d78 \ + --hash=sha256:c12900e1903e289deb84eb893dca88591d6884d3e3cda4fb711b8812118416e8 \ + --hash=sha256:c42735cc68fdb4c6065cf0a0701dfff3a12a1734c63a36376349af9a5481f27b \ + --hash=sha256:c4d41c83c8a2b8d8030204391ebeb6174fbdb044f0331653c4b5a4ce4150bcc0 \ + --hash=sha256:ce4e165a1436697e5e39587c4fba24db4545a5c9801e0d749f1afd09ad3ab901 \ + --hash=sha256:cf35a25be3eb6263b2e0544ba26294651113fab79064f994d347a2ca5973e8e2 \ + --hash=sha256:d0762f4232bce2c7f6c0af329db8b821b4460bbe123a2528fb5677d03db7a4b5 \ + --hash=sha256:dba457ff87c39cbae3c5020475a728d24bbd9d00376df9af9724cd3bb59ff07a \ + --hash=sha256:df4aa0782a53032fd35e18dcd6d328d6126324bfcfdef0cb5c2856f25b4b6f94 \ + --hash=sha256:e6cbc914d77678db0c8bc39e723d994174633d18f9d6be4665ec29cce978a96d \ + --hash=sha256:e732a75fcb6b84872b168f972fbbf3749baf76308635f14015d1d35ed0c5719c \ + --hash=sha256:ed4034939d8566ccdcd3b4e4f23c63fd807fb8763ae5668d59a19e11640a8242 # via python3-saml -zipp==3.21.0 +zipp==3.21.0 \ + --hash=sha256:2c9958f6430a2040341a52eb608ed6dd93ef4392e02ffe219417c1b28b5dd1f4 \ + --hash=sha256:ac1bbe05fd2991f160ebce24ffbac5f6d11d83dc90891255885223d42b3cd931 # via importlib-metadata -zopfli==0.2.3.post1 +zopfli==0.2.3.post1 \ + --hash=sha256:0aa5f90d6298bda02a95bc8dc8c3c19004d5a4e44bda00b67ca7431d857b4b54 \ + --hash=sha256:0cc20b02a9531559945324c38302fd4ba763311632d0ec8a1a0aa9c10ea363e6 \ + --hash=sha256:1d8cc06605519e82b16df090e17cb3990d1158861b2872c3117f1168777b81e4 \ + --hash=sha256:1f990634fd5c5c8ced8edddd8bd45fab565123b4194d6841e01811292650acae \ + --hash=sha256:2345e713260a350bea0b01a816a469ea356bc2d63d009a0d777691ecbbcf7493 \ + --hash=sha256:2768c877f76c8a0e7519b1c86c93757f3c01492ddde55751e9988afb7eff64e1 \ + --hash=sha256:29ea74e72ffa6e291b8c6f2504ce6c146b4fe990c724c1450eb8e4c27fd31431 \ + --hash=sha256:34a99592f3d9eb6f737616b5bd74b48a589fdb3cb59a01a50d636ea81d6af272 \ + --hash=sha256:3654bfc927bc478b1c3f3ff5056ed7b20a1a37fa108ca503256d0a699c03bbb1 \ + --hash=sha256:3657e416ffb8f31d9d3424af12122bb251befae109f2e271d87d825c92fc5b7b \ + --hash=sha256:37d011e92f7b9622742c905fdbed9920a1d0361df84142807ea2a528419dea7f \ + --hash=sha256:3827170de28faf144992d3d4dcf8f3998fe3c8a6a6f4a08f1d42c2ec6119d2bb \ + --hash=sha256:39e576f93576c5c223b41d9c780bbb91fd6db4babf3223d2a4fe7bf568e2b5a8 \ + --hash=sha256:3a89277ed5f8c0fb2d0b46d669aa0633123aa7381f1f6118c12f15e0fb48f8ca \ + --hash=sha256:3c163911f8bad94b3e1db0a572e7c28ba681a0c91d0002ea1e4fa9264c21ef17 \ + --hash=sha256:3f0197b6aa6eb3086ae9e66d6dd86c4d502b6c68b0ec490496348ae8c05ecaef \ + --hash=sha256:48dba9251060289101343110ab47c0756f66f809bb4d1ddbb6d5c7e7752115c5 \ + --hash=sha256:4915a41375bdee4db749ecd07d985a0486eb688a6619f713b7bf6fbfd145e960 \ + --hash=sha256:4c1226a7e2c7105ac31503a9bb97454743f55d88164d6d46bc138051b77f609b \ + --hash=sha256:4e50ffac74842c1c1018b9b73875a0d0a877c066ab06bf7cccbaa84af97e754f \ + --hash=sha256:518f1f4ed35dd69ce06b552f84e6d081f07c552b4c661c5312d950a0b764a58a \ + --hash=sha256:5aad740b4d4fcbaaae4887823925166ffd062db3b248b3f432198fc287381d1a \ + --hash=sha256:5f272186e03ad55e7af09ab78055535c201b1a0bcc2944edb1768298d9c483a4 \ + --hash=sha256:5fcfc0dc2761e4fcc15ad5d273b4d58c2e8e059d3214a7390d4d3c8e2aee644e \ + --hash=sha256:60db20f06c3d4c5934b16cfa62a2cc5c3f0686bffe0071ed7804d3c31ab1a04e \ + --hash=sha256:615a8ac9dda265e9cc38b2a76c3142e4a9f30fea4a79c85f670850783bc6feb4 \ + --hash=sha256:6482db9876c68faac2d20a96b566ffbf65ddaadd97b222e4e73641f4f8722fc4 \ + --hash=sha256:6617fb10f9e4393b331941861d73afb119cd847e88e4974bdbe8068ceef3f73f \ + --hash=sha256:676919fba7311125244eb0c4393679ac5fe856e5864a15d122bd815205369fa0 \ + --hash=sha256:6c2d2bc8129707e34c51f9352c4636ca313b52350bbb7e04637c46c1818a2a70 \ + --hash=sha256:71390dbd3fbf6ebea9a5d85ffed8c26ee1453ee09248e9b88486e30e0397b775 \ + --hash=sha256:716cdbfc57bfd3d3e31a58e6246e8190e6849b7dbb7c4ce39ef8bbf0edb8f6d5 \ + --hash=sha256:75a26a2307b10745a83b660c404416e984ee6fca515ec7f0765f69af3ce08072 \ + --hash=sha256:7be5cc6732eb7b4df17305d8a7b293223f934a31783a874a01164703bc1be6cd \ + --hash=sha256:7cce242b5df12b2b172489daf19c32e5577dd2fac659eb4b17f6a6efb446fd5c \ + --hash=sha256:81c341d9bb87a6dbbb0d45d6e272aca80c7c97b4b210f9b6e233bf8b87242f29 \ + --hash=sha256:89899641d4de97dbad8e0cde690040d078b6aea04066dacaab98e0b5a23573f2 \ + --hash=sha256:8d5ab297d660b75c159190ce6d73035502310e40fd35170aed7d1a1aea7ddd65 \ + --hash=sha256:8fbe5bcf10d01aab3513550f284c09fef32f342b36f56bfae2120a9c4d12c130 \ + --hash=sha256:91a2327a4d7e77471fa4fbb26991c6de4a738c6fc6a33e09bb25f56a870a4b7b \ + --hash=sha256:95a260cafd56b8fffa679918937401c80bb38e1681c448b988022e4c3610965d \ + --hash=sha256:96484dc0f48be1c5d7ae9f38ed1ce41e3675fd506b27c11a6607f14b49101e99 \ + --hash=sha256:9a6aec38a989bad7ddd1ef53f1265699e49e294d08231b5313d61293f3cd6237 \ + --hash=sha256:9ba214f4f45bec195ee8559651154d3ac2932470b9d91c5715fc29c013349f8c \ + --hash=sha256:9f4a7ec2770e6af05f5a02733fd3900f30a9cd58e5d6d3727e14c5bcd6e7d587 \ + --hash=sha256:a1cf720896d2ce998bc8e051d4b4ce0d8bec007aab6243102e8e1d22a0b2fb3f \ + --hash=sha256:a241a68581d34d67b40c425cce3d1fd211c092f99d9250947824ccba9f491949 \ + --hash=sha256:a53b18797cdef27e019db595d66c4b077325afe2fd62145953275f53d84ce40c \ + --hash=sha256:a82fc2dbebe6eb908b9c665e71496f8525c1bc4d2e3a7a7722ef2b128b6227c8 \ + --hash=sha256:a86eb88e06bd87e1fff31dac878965c26b0c26db59ddcf78bb0379a954b120de \ + --hash=sha256:aa588b21044f8a74e423d8c8a4c7fc9988501878aacced793467010039c50734 \ + --hash=sha256:b05296e8bc88c92e2b21e0a9bae4740c1551ee613c1d93a51fd28a7a0b2b6fbb \ + --hash=sha256:b0ec13f352ea5ae0fc91f98a48540512eed0767d0ec4f7f3cb92d92797983d18 \ + --hash=sha256:b3df42f52502438ee973042cc551877d24619fa1cd38ef7b7e9ac74200daca8b \ + --hash=sha256:b78008a69300d929ca2efeffec951b64a312e9a811e265ea4a907ab546d79fa6 \ + --hash=sha256:b9026a21b6d41eb0e2e63f5bc1242c3fcc43ecb770963cda99a4307863dac12e \ + --hash=sha256:bbe429fc50686bb2a2608a30843e36fbaa123462a5284f136c7d9e0145220bfd \ + --hash=sha256:bfa1eb759e07d8b7aa7a310a2bc535e127ee70addf90dc8d4b946b593c3e51a8 \ + --hash=sha256:c1e0ed5d84ffa2d677cc9582fc01e61dab2e7ef8b8996e055f0a76167b1b94df \ + --hash=sha256:c4278d1873ce6e803e5d4f8d702fd3026bd67fca744aa98881324d1157ddf748 \ + --hash=sha256:cac2b37ab21c2b36a10b685b1893ebd6b0f83ae26004838ac817680881576567 \ + --hash=sha256:cbe6df25807227519debd1a57ab236f5f6bad441500e85b13903e51f93a43214 \ + --hash=sha256:cd2c002f160502608dcc822ed2441a0f4509c52e86fcfd1a09e937278ed1ca14 \ + --hash=sha256:e0137dd64a493ba6a4be37405cfd6febe650a98cc1e9dca8f6b8c63b1db11b41 \ + --hash=sha256:e63d558847166543c2c9789e6f985400a520b7eacc4b99181668b2c3aeadd352 \ + --hash=sha256:eb45a34f23da4f8bc712b6376ca5396914b0b7c09adbb001dad964eb7f3132f8 \ + --hash=sha256:ecb7572df5372abce8073df078207d9d1749f20b8b136089916a4a0868d56051 \ + --hash=sha256:f12000a6accdd4bf0a3fa6eaa1b1c7a7bc80af0a2edf3f89d770d3dcce1d0e22 \ + --hash=sha256:f7d69c1a7168ad0e9cb864e8663acb232986a0c9c9cb9801f56bf6214f53a54d \ + --hash=sha256:f815fcc2b2a457977724bad97fb4854022980f51ce7b136925e336b530545ae1 \ + --hash=sha256:fc39f5c27f962ec8660d8d20c24762431131b5d8c672b44b0a54cf2b5bcde9b9 # via fonttools diff --git a/tasks.py b/tasks.py index 9a10d53cd7c3..07f49898a3ed 100644 --- a/tasks.py +++ b/tasks.py @@ -365,14 +365,14 @@ def install(c, uv=False, skip_plugins=False): ) run( c, - f'pip3 install --no-cache-dir --disable-pip-version-check -U -r {INSTALL_FILE}', + f'pip3 install --no-cache-dir --disable-pip-version-check -U --require-hashes -r {INSTALL_FILE}', ) else: run( c, 'pip3 install --no-cache-dir --disable-pip-version-check -U uv setuptools', ) - run(c, f'uv pip install -U -r {INSTALL_FILE}') + run(c, f'uv pip install -U --require-hashes -r {INSTALL_FILE}') # Run plugins install if not skip_plugins: From 9389fc6eb52178a5475279aa8451e69677c98f54 Mon Sep 17 00:00:00 2001 From: Matthias Mair Date: Thu, 6 Feb 2025 21:48:26 +0100 Subject: [PATCH 124/156] remove usage of git repo --- src/backend/requirements.in | 2 +- src/backend/requirements.txt | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/backend/requirements.in b/src/backend/requirements.in index 375af4dcc9f5..b55edd6189de 100644 --- a/src/backend/requirements.in +++ b/src/backend/requirements.in @@ -2,7 +2,7 @@ Django<5.0 # Django package coreapi # API documentation for djangorestframework cryptography>=40.0.0,!=40.0.2,<=43.0.3 # Core cryptographic functionality -django-allauth[mfa,socialaccount,saml,openid] @ git+https://codeberg.org/allauth/django-allauth@main # SSO for external providers via OpenID +django-allauth[mfa,socialaccount,saml,openid] # SSO for external providers via OpenID django-cleanup # Automated deletion of old / unused uploaded files django-cors-headers # CORS headers extension for DRF django-dbbackup # Backup / restore of database and media files diff --git a/src/backend/requirements.txt b/src/backend/requirements.txt index 1f65c0ba2f53..5e37ba03073c 100644 --- a/src/backend/requirements.txt +++ b/src/backend/requirements.txt @@ -412,7 +412,8 @@ django==4.2.18 \ # djangorestframework # djangorestframework-simplejwt # drf-spectacular -django-allauth[mfa, openid, saml, socialaccount] @ git+https://codeberg.org/allauth/django-allauth@3428a0e202e996d62d919a1e458f34131a77a640 +django-allauth[mfa, openid, saml, socialaccount]==65.4.0 \ + --hash=sha256:1b6eb5d4a781a9a18dcd5ccf2bcae7ae542a56baea8e7b8f33a54c743cb54b0a # via -r src/backend/requirements.in django-cleanup==9.0.0 \ --hash=sha256:19f8b0e830233f9f0f683b17181f414672a0f48afe3ea3cc80ba47ae40ad880c \ From 6ee269b218d15db95dcf7a26b164b0f4388a4411 Mon Sep 17 00:00:00 2001 From: Matthias Mair Date: Thu, 6 Feb 2025 22:19:12 +0100 Subject: [PATCH 125/156] fix doc string --- docs/docs/start/config.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/docs/start/config.md b/docs/docs/start/config.md index 3910f46fe64f..45714832f6a0 100644 --- a/docs/docs/start/config.md +++ b/docs/docs/start/config.md @@ -380,7 +380,7 @@ InvenTree provides allowance for additional sign-in options. The following optio | Environment Variable | Configuration File | Description | Default | | --- | --- | --- | --- | | INVENTREE_MFA_ENABLED | mfa_enabled | Enable or disable multi-factor authentication support for the InvenTree server | True | -| MFA_SUPPORTED_TYPES | mfa_supported_types | List of supported multi-factor authentication types | recovery_codes,totp | +| INVENTREE_MFA_SUPPORTED_TYPES | mfa_supported_types | List of supported multi-factor authentication types | recovery_codes,totp | ### Single Sign On From c5a35be011c8392be0b3bca9665188a82719670e Mon Sep 17 00:00:00 2001 From: Matthias Mair Date: Fri, 7 Feb 2025 08:29:28 +0100 Subject: [PATCH 126/156] extend schema generation to just patch in allauth --- .../InvenTree/management/commands/schema.py | 34 +++++++++++++++++++ tasks.py | 2 +- 2 files changed, 35 insertions(+), 1 deletion(-) create mode 100644 src/backend/InvenTree/InvenTree/management/commands/schema.py diff --git a/src/backend/InvenTree/InvenTree/management/commands/schema.py b/src/backend/InvenTree/InvenTree/management/commands/schema.py new file mode 100644 index 000000000000..7b28b3fed871 --- /dev/null +++ b/src/backend/InvenTree/InvenTree/management/commands/schema.py @@ -0,0 +1,34 @@ +"""Check if there are any pending database migrations, and run them.""" + +from pathlib import Path + +from django.conf import settings + +import structlog +import yaml +from drf_spectacular.management.commands import spectacular + +logger = structlog.get_logger('inventree') + + +class Command(spectacular.Command): + """Overwritten command to include allauth schemas.""" + + def handle(self, *args, **kwargs): + """Extended schema generation that patches in allauth schemas.""" + from allauth.headless.spec.internal import schema + + path = Path(schema.__file__).parent.parent / 'doc/openapi.yaml' + with open(path, 'rb') as f: + spec = yaml.safe_load(f) + spec_paths = spec['paths'] + # add 'operationId' to each path + for path, path_spec in spec_paths.items(): + for method, method_spec in path_spec.items(): + operation_id = method_spec.get('operationId', None) + if operation_id is None: + method_spec['operationId'] = f'{method}{path}' + + settings.SPECTACULAR_SETTINGS['APPEND_PATHS'] = spec_paths + + super().handle(*args, **kwargs) diff --git a/tasks.py b/tasks.py index 07f49898a3ed..54fd141a463e 100644 --- a/tasks.py +++ b/tasks.py @@ -1220,7 +1220,7 @@ def schema( info(f"Exporting schema file to '{filename}'") - cmd = f'spectacular --file {filename} --validate --color' + cmd = f'schema --file {filename} --validate --color' if not ignore_warnings: cmd += ' --fail-on-warn' From 8ce97803adda2c30d7bd11ae948a9cdaddac1d32 Mon Sep 17 00:00:00 2001 From: Matthias Mair Date: Sun, 9 Feb 2025 01:41:12 +0100 Subject: [PATCH 127/156] patch allauth OAI ref names --- .../InvenTree/management/commands/schema.py | 39 +++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/src/backend/InvenTree/InvenTree/management/commands/schema.py b/src/backend/InvenTree/InvenTree/management/commands/schema.py index 7b28b3fed871..af17e0e633dd 100644 --- a/src/backend/InvenTree/InvenTree/management/commands/schema.py +++ b/src/backend/InvenTree/InvenTree/management/commands/schema.py @@ -11,13 +11,38 @@ logger = structlog.get_logger('inventree') +def prep_name(ref): + """Prepend allauth to all ref names.""" + return f'allauth.{ref}' + + class Command(spectacular.Command): """Overwritten command to include allauth schemas.""" + def proccess_refs(self, value: list | dict) -> list | dict: + """Prepend ref names.""" + + def sub_component_name(name: str) -> str: + s = name.split('/') + if len(s) == 4 and s[1] == 'components': + s[3] = prep_name(s[3]) + return '/'.join(s) + + if isinstance(value, list): + return [{k: sub_component_name(v) for k, v in val.items()} for val in value] + elif isinstance(value, dict): + return { + k: sub_component_name(v) + if isinstance(v, str) + else self.proccess_refs(v) + for k, v in value.items() + } + def handle(self, *args, **kwargs): """Extended schema generation that patches in allauth schemas.""" from allauth.headless.spec.internal import schema + # paths path = Path(schema.__file__).parent.parent / 'doc/openapi.yaml' with open(path, 'rb') as f: spec = yaml.safe_load(f) @@ -28,7 +53,21 @@ def handle(self, *args, **kwargs): operation_id = method_spec.get('operationId', None) if operation_id is None: method_spec['operationId'] = f'{method}{path}' + # update parameters, resresponses, etc. + for key, value in method_spec.items(): + if key in ['parameters', 'responses', 'requestBody']: + method_spec[key] = self.proccess_refs(value) settings.SPECTACULAR_SETTINGS['APPEND_PATHS'] = spec_paths + # components + components = spec['components'] + # Prepend all component names with 'allauth.' + for component_name, component in components.items(): + new_component = {} + for sub_component_name, sub_component in component.items(): + new_component[prep_name(sub_component_name)] = sub_component + components[component_name] = new_component + settings.SPECTACULAR_SETTINGS['APPEND_COMPONENTS'] = components + super().handle(*args, **kwargs) From d4379337e85ad0ebcf51d9e16535a9112bb1cfbf Mon Sep 17 00:00:00 2001 From: Matthias Mair Date: Sun, 9 Feb 2025 01:45:54 +0100 Subject: [PATCH 128/156] reduce types --- src/backend/InvenTree/InvenTree/management/commands/schema.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/backend/InvenTree/InvenTree/management/commands/schema.py b/src/backend/InvenTree/InvenTree/management/commands/schema.py index af17e0e633dd..0841f8d65773 100644 --- a/src/backend/InvenTree/InvenTree/management/commands/schema.py +++ b/src/backend/InvenTree/InvenTree/management/commands/schema.py @@ -19,7 +19,7 @@ def prep_name(ref): class Command(spectacular.Command): """Overwritten command to include allauth schemas.""" - def proccess_refs(self, value: list | dict) -> list | dict: + def proccess_refs(self, value): """Prepend ref names.""" def sub_component_name(name: str) -> str: From 2936d9c7f4aac7a1aa13d611b9a6dbc8cb02e0d6 Mon Sep 17 00:00:00 2001 From: Matthias Mair Date: Sun, 9 Feb 2025 02:15:43 +0100 Subject: [PATCH 129/156] refactor code structure --- .../InvenTree/management/commands/schema.py | 55 +++++++++++-------- 1 file changed, 33 insertions(+), 22 deletions(-) diff --git a/src/backend/InvenTree/InvenTree/management/commands/schema.py b/src/backend/InvenTree/InvenTree/management/commands/schema.py index 0841f8d65773..1071c2e4f54d 100644 --- a/src/backend/InvenTree/InvenTree/management/commands/schema.py +++ b/src/backend/InvenTree/InvenTree/management/commands/schema.py @@ -11,13 +11,17 @@ logger = structlog.get_logger('inventree') +dja_path_prefix = '/_allauth/{client}/v1/' +dja_ref_prefix = 'allauth' + + def prep_name(ref): - """Prepend allauth to all ref names.""" - return f'allauth.{ref}' + """Prepend django-allauth to all ref names.""" + return f'{dja_ref_prefix}.{ref}' class Command(spectacular.Command): - """Overwritten command to include allauth schemas.""" + """Overwritten command to include django-allauth schemas.""" def proccess_refs(self, value): """Prepend ref names.""" @@ -39,34 +43,41 @@ def sub_component_name(name: str) -> str: } def handle(self, *args, **kwargs): - """Extended schema generation that patches in allauth schemas.""" + """Extended schema generation that patches in django-allauth schemas.""" from allauth.headless.spec.internal import schema - # paths - path = Path(schema.__file__).parent.parent / 'doc/openapi.yaml' - with open(path, 'rb') as f: + # gather paths + org_path = Path(schema.__file__).parent.parent / 'doc/openapi.yaml' + with open(org_path, 'rb') as f: spec = yaml.safe_load(f) - spec_paths = spec['paths'] - # add 'operationId' to each path - for path, path_spec in spec_paths.items(): - for method, method_spec in path_spec.items(): - operation_id = method_spec.get('operationId', None) - if operation_id is None: - method_spec['operationId'] = f'{method}{path}' - # update parameters, resresponses, etc. + + paths = {} + # Reformat paths + for path_name, path_spec in spec['paths'].items(): + # strip path name + path_name = path_name.removeprefix(dja_path_prefix) + + # fix refs + for method_name, method_spec in path_spec.items(): + if method_spec.get('operationId', None) is None: + method_spec['operationId'] = ( + f'{path_name.replace("/", "_")}_{method_name}' + ) + # update all refs for key, value in method_spec.items(): if key in ['parameters', 'responses', 'requestBody']: method_spec[key] = self.proccess_refs(value) - settings.SPECTACULAR_SETTINGS['APPEND_PATHS'] = spec_paths + # prefix path name + paths[f'/api/auth/v1/{path_name}'] = path_spec + settings.SPECTACULAR_SETTINGS['APPEND_PATHS'] = paths - # components - components = spec['components'] - # Prepend all component names with 'allauth.' - for component_name, component in components.items(): + components = {} + # Reformat components + for component_name, component_spec in spec['components'].items(): new_component = {} - for sub_component_name, sub_component in component.items(): - new_component[prep_name(sub_component_name)] = sub_component + for subcomponent_name, subcomponent_spec in component_spec.items(): + new_component[prep_name(subcomponent_name)] = subcomponent_spec components[component_name] = new_component settings.SPECTACULAR_SETTINGS['APPEND_COMPONENTS'] = components From 8433a9eddab2743d66a17543835ef4b81b3882c9 Mon Sep 17 00:00:00 2001 From: Matthias Mair Date: Sun, 9 Feb 2025 02:36:01 +0100 Subject: [PATCH 130/156] fix ref patching a bit more --- .../InvenTree/management/commands/schema.py | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/src/backend/InvenTree/InvenTree/management/commands/schema.py b/src/backend/InvenTree/InvenTree/management/commands/schema.py index 1071c2e4f54d..1f15316ddda7 100644 --- a/src/backend/InvenTree/InvenTree/management/commands/schema.py +++ b/src/backend/InvenTree/InvenTree/management/commands/schema.py @@ -27,13 +27,20 @@ def proccess_refs(self, value): """Prepend ref names.""" def sub_component_name(name: str) -> str: + if not isinstance(name, str): + return name s = name.split('/') if len(s) == 4 and s[1] == 'components': s[3] = prep_name(s[3]) return '/'.join(s) if isinstance(value, list): - return [{k: sub_component_name(v) for k, v in val.items()} for val in value] + return [ + {k: sub_component_name(v) for k, v in val.items()} + if isinstance(val, dict) + else val + for val in value + ] elif isinstance(value, dict): return { k: sub_component_name(v) @@ -61,7 +68,7 @@ def handle(self, *args, **kwargs): for method_name, method_spec in path_spec.items(): if method_spec.get('operationId', None) is None: method_spec['operationId'] = ( - f'{path_name.replace("/", "_")}_{method_name}' + f'{dja_ref_prefix}_{path_name.replace("/", "_")}_{method_name}' ) # update all refs for key, value in method_spec.items(): @@ -77,7 +84,9 @@ def handle(self, *args, **kwargs): for component_name, component_spec in spec['components'].items(): new_component = {} for subcomponent_name, subcomponent_spec in component_spec.items(): - new_component[prep_name(subcomponent_name)] = subcomponent_spec + new_component[prep_name(subcomponent_name)] = self.proccess_refs( + subcomponent_spec + ) components[component_name] = new_component settings.SPECTACULAR_SETTINGS['APPEND_COMPONENTS'] = components From 241f4de079111d4c8f13fe5787257ebb81d56ef3 Mon Sep 17 00:00:00 2001 From: Matthias Mair Date: Sun, 9 Feb 2025 03:20:00 +0100 Subject: [PATCH 131/156] add param cleanup --- .../InvenTree/management/commands/schema.py | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/src/backend/InvenTree/InvenTree/management/commands/schema.py b/src/backend/InvenTree/InvenTree/management/commands/schema.py index 1f15316ddda7..659a6680593e 100644 --- a/src/backend/InvenTree/InvenTree/management/commands/schema.py +++ b/src/backend/InvenTree/InvenTree/management/commands/schema.py @@ -13,6 +13,10 @@ dja_path_prefix = '/_allauth/{client}/v1/' dja_ref_prefix = 'allauth' +dja_clean_params = [ + '#/components/parameters/allauth.SessionToken', + '#/components/parameters/allauth.Client', +] def prep_name(ref): @@ -20,6 +24,14 @@ def prep_name(ref): return f'{dja_ref_prefix}.{ref}' +def clean_params(params): + """Clean refs of unwanted parameters. + + We don't use them in our API, we only support allauths browser APIs endpoints. + """ + return [p for p in params if p['$ref'] not in dja_clean_params] + + class Command(spectacular.Command): """Overwritten command to include django-allauth schemas.""" @@ -75,6 +87,10 @@ def handle(self, *args, **kwargs): if key in ['parameters', 'responses', 'requestBody']: method_spec[key] = self.proccess_refs(value) + # patch out unwanted parameters - we don't use it + if params := method_spec.get('parameters', None): + method_spec['parameters'] = clean_params(params) + # prefix path name paths[f'/api/auth/v1/{path_name}'] = path_spec settings.SPECTACULAR_SETTINGS['APPEND_PATHS'] = paths From c4b287a96a0c2ca6acc828826377ea3d222bd7f2 Mon Sep 17 00:00:00 2001 From: Matthias Mair Date: Sun, 9 Feb 2025 03:21:13 +0100 Subject: [PATCH 132/156] ensure strings, number, bools are handled correctly in cleanup --- .../InvenTree/management/commands/schema.py | 19 ++++++------------- 1 file changed, 6 insertions(+), 13 deletions(-) diff --git a/src/backend/InvenTree/InvenTree/management/commands/schema.py b/src/backend/InvenTree/InvenTree/management/commands/schema.py index 659a6680593e..764677bdbb2f 100644 --- a/src/backend/InvenTree/InvenTree/management/commands/schema.py +++ b/src/backend/InvenTree/InvenTree/management/commands/schema.py @@ -46,20 +46,13 @@ def sub_component_name(name: str) -> str: s[3] = prep_name(s[3]) return '/'.join(s) - if isinstance(value, list): - return [ - {k: sub_component_name(v) for k, v in val.items()} - if isinstance(val, dict) - else val - for val in value - ] + if isinstance(value, str): + return sub_component_name(value) + elif isinstance(value, list): + return [self.proccess_refs(v) for v in value] elif isinstance(value, dict): - return { - k: sub_component_name(v) - if isinstance(v, str) - else self.proccess_refs(v) - for k, v in value.items() - } + return {k: self.proccess_refs(v) for k, v in value.items()} + return value def handle(self, *args, **kwargs): """Extended schema generation that patches in django-allauth schemas.""" From e3ea9b321852470950534f660e32440d2a899500 Mon Sep 17 00:00:00 2001 From: Matthias Mair Date: Sun, 9 Feb 2025 03:23:51 +0100 Subject: [PATCH 133/156] move fnc --- .../InvenTree/management/commands/schema.py | 21 +++++++++++-------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/src/backend/InvenTree/InvenTree/management/commands/schema.py b/src/backend/InvenTree/InvenTree/management/commands/schema.py index 764677bdbb2f..4f70e4f4b40a 100644 --- a/src/backend/InvenTree/InvenTree/management/commands/schema.py +++ b/src/backend/InvenTree/InvenTree/management/commands/schema.py @@ -1,6 +1,7 @@ """Check if there are any pending database migrations, and run them.""" from pathlib import Path +from typing import TypeVar from django.conf import settings @@ -8,6 +9,7 @@ import yaml from drf_spectacular.management.commands import spectacular +T = TypeVar('T') logger = structlog.get_logger('inventree') @@ -24,6 +26,16 @@ def prep_name(ref): return f'{dja_ref_prefix}.{ref}' +def sub_component_name(name: T) -> T: + """Clean up component references.""" + if not isinstance(name, str): + return name + s = name.split('/') + if len(s) == 4 and s[1] == 'components': + s[3] = prep_name(s[3]) + return '/'.join(s) + + def clean_params(params): """Clean refs of unwanted parameters. @@ -37,15 +49,6 @@ class Command(spectacular.Command): def proccess_refs(self, value): """Prepend ref names.""" - - def sub_component_name(name: str) -> str: - if not isinstance(name, str): - return name - s = name.split('/') - if len(s) == 4 and s[1] == 'components': - s[3] = prep_name(s[3]) - return '/'.join(s) - if isinstance(value, str): return sub_component_name(value) elif isinstance(value, list): From c1e3586bd81488896a286e29e1474556724fda1e Mon Sep 17 00:00:00 2001 From: Matthias Mair Date: Sun, 9 Feb 2025 03:26:42 +0100 Subject: [PATCH 134/156] shorten names --- .../InvenTree/management/commands/schema.py | 32 +++++++++---------- 1 file changed, 15 insertions(+), 17 deletions(-) diff --git a/src/backend/InvenTree/InvenTree/management/commands/schema.py b/src/backend/InvenTree/InvenTree/management/commands/schema.py index 4f70e4f4b40a..f552a818c39b 100644 --- a/src/backend/InvenTree/InvenTree/management/commands/schema.py +++ b/src/backend/InvenTree/InvenTree/management/commands/schema.py @@ -68,38 +68,36 @@ def handle(self, *args, **kwargs): paths = {} # Reformat paths - for path_name, path_spec in spec['paths'].items(): + for p_name, p_spec in spec['paths'].items(): # strip path name - path_name = path_name.removeprefix(dja_path_prefix) + p_name = p_name.removeprefix(dja_path_prefix) # fix refs - for method_name, method_spec in path_spec.items(): - if method_spec.get('operationId', None) is None: - method_spec['operationId'] = ( - f'{dja_ref_prefix}_{path_name.replace("/", "_")}_{method_name}' + for m_name, m_spec in p_spec.items(): + if m_spec.get('operationId', None) is None: + m_spec['operationId'] = ( + f'{dja_ref_prefix}_{p_name.replace("/", "_")}_{m_name}' ) # update all refs - for key, value in method_spec.items(): + for key, value in m_spec.items(): if key in ['parameters', 'responses', 'requestBody']: - method_spec[key] = self.proccess_refs(value) + m_spec[key] = self.proccess_refs(value) # patch out unwanted parameters - we don't use it - if params := method_spec.get('parameters', None): - method_spec['parameters'] = clean_params(params) + if params := m_spec.get('parameters', None): + m_spec['parameters'] = clean_params(params) # prefix path name - paths[f'/api/auth/v1/{path_name}'] = path_spec + paths[f'/api/auth/v1/{p_name}'] = p_spec settings.SPECTACULAR_SETTINGS['APPEND_PATHS'] = paths components = {} # Reformat components - for component_name, component_spec in spec['components'].items(): + for c_name, c_spec in spec['components'].items(): new_component = {} - for subcomponent_name, subcomponent_spec in component_spec.items(): - new_component[prep_name(subcomponent_name)] = self.proccess_refs( - subcomponent_spec - ) - components[component_name] = new_component + for sc_name, sc_spec in c_spec.items(): + new_component[prep_name(sc_name)] = self.proccess_refs(sc_spec) + components[c_name] = new_component settings.SPECTACULAR_SETTINGS['APPEND_COMPONENTS'] = components super().handle(*args, **kwargs) From 38db8c6ce188b9fbc8194ac8ccbd621c6fa29ef4 Mon Sep 17 00:00:00 2001 From: Matthias Mair Date: Sun, 9 Feb 2025 04:03:58 +0100 Subject: [PATCH 135/156] bump allauth --- src/backend/requirements.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/backend/requirements.txt b/src/backend/requirements.txt index 5e37ba03073c..462f617e979f 100644 --- a/src/backend/requirements.txt +++ b/src/backend/requirements.txt @@ -412,8 +412,8 @@ django==4.2.18 \ # djangorestframework # djangorestframework-simplejwt # drf-spectacular -django-allauth[mfa, openid, saml, socialaccount]==65.4.0 \ - --hash=sha256:1b6eb5d4a781a9a18dcd5ccf2bcae7ae542a56baea8e7b8f33a54c743cb54b0a +django-allauth[mfa, openid, saml, socialaccount]==65.4.1 \ + --hash=sha256:60b32aef7dbbcc213319aa4fd8f570e985266ea1162ae6ef7a26a24efca85c8c # via -r src/backend/requirements.in django-cleanup==9.0.0 \ --hash=sha256:19f8b0e830233f9f0f683b17181f414672a0f48afe3ea3cc80ba47ae40ad880c \ From c6ae8ca11af9ccd56ff5b3b7fd27f58357d9529d Mon Sep 17 00:00:00 2001 From: Matthias Mair Date: Sun, 9 Feb 2025 04:23:45 +0100 Subject: [PATCH 136/156] re-add auth doc section --- docs/docs/api/schema.md | 1 + docs/docs/api/schema/auth.md | 8 ++++++++ docs/extract_schema.py | 1 + 3 files changed, 10 insertions(+) create mode 100644 docs/docs/api/schema/auth.md diff --git a/docs/docs/api/schema.md b/docs/docs/api/schema.md index 8b686b403646..441e977e2e9a 100644 --- a/docs/docs/api/schema.md +++ b/docs/docs/api/schema.md @@ -22,6 +22,7 @@ API schema documentation is split into the following categories: | Category | Description | | --- | --- | +| [Authorization and Authentication](./schema/auth.md) | Authorization and Authentication | | [Background Task Management](./schema/background-task.md) | Background Task Management | | [Barcode Scanning](./schema/barcode.md) | Barcode Scanning | | [Bill of Materials](./schema/bom.md) | Bill of Materials | diff --git a/docs/docs/api/schema/auth.md b/docs/docs/api/schema/auth.md new file mode 100644 index 000000000000..ad4ef5b0b8a0 --- /dev/null +++ b/docs/docs/api/schema/auth.md @@ -0,0 +1,8 @@ + +--- +title: Authorization and Authentication +--- + +The *Authorization and Authentication* section of the InvenTree API schema is documented below. + +[OAD(./docs/docs/api/schema/auth.yml)] diff --git a/docs/extract_schema.py b/docs/extract_schema.py index 647a4a7b6ea2..b6b70a9db1ff 100644 --- a/docs/extract_schema.py +++ b/docs/extract_schema.py @@ -14,6 +14,7 @@ # List of special paths we want to split out SPECIAL_PATHS = { + 'auth': 'Authorization and Authentication', 'background-task': 'Background Task Management', 'barcode': 'Barcode Scanning', 'bom': 'Bill of Materials', From 5a94b03a3bdd375f075074aaf0d368a760692a5d Mon Sep 17 00:00:00 2001 From: Matthias Mair Date: Sun, 9 Feb 2025 04:24:16 +0100 Subject: [PATCH 137/156] fix doc structure --- docs/docs/api/schema/auth.md | 1 - 1 file changed, 1 deletion(-) diff --git a/docs/docs/api/schema/auth.md b/docs/docs/api/schema/auth.md index ad4ef5b0b8a0..62c8b50103ed 100644 --- a/docs/docs/api/schema/auth.md +++ b/docs/docs/api/schema/auth.md @@ -1,4 +1,3 @@ - --- title: Authorization and Authentication --- From d18ddcf06825ac7538e15ed9366900249c3c1230 Mon Sep 17 00:00:00 2001 From: Matthias Mair Date: Sun, 9 Feb 2025 13:40:16 +0100 Subject: [PATCH 138/156] revert playwrigth change --- src/frontend/playwright.config.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/frontend/playwright.config.ts b/src/frontend/playwright.config.ts index 67ab21c0ad22..67d243a111b4 100644 --- a/src/frontend/playwright.config.ts +++ b/src/frontend/playwright.config.ts @@ -5,7 +5,7 @@ export default defineConfig({ fullyParallel: true, timeout: 90000, forbidOnly: !!process.env.CI, - retries: process.env.CI ? 3 : 0, + retries: process.env.CI ? 2 : 0, workers: process.env.CI ? 3 : undefined, reporter: process.env.CI ? [['html', { open: 'never' }], ['github']] : 'list', From 1d0ce5bc3a696b623df11e07785c6a08495338ba Mon Sep 17 00:00:00 2001 From: Matthias Mair Date: Sun, 9 Feb 2025 13:41:17 +0100 Subject: [PATCH 139/156] ckean up browser only path --- src/backend/InvenTree/InvenTree/management/commands/schema.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/backend/InvenTree/InvenTree/management/commands/schema.py b/src/backend/InvenTree/InvenTree/management/commands/schema.py index f552a818c39b..4f47ce57ebed 100644 --- a/src/backend/InvenTree/InvenTree/management/commands/schema.py +++ b/src/backend/InvenTree/InvenTree/management/commands/schema.py @@ -70,7 +70,9 @@ def handle(self, *args, **kwargs): # Reformat paths for p_name, p_spec in spec['paths'].items(): # strip path name - p_name = p_name.removeprefix(dja_path_prefix) + p_name = p_name.removeprefix(dja_path_prefix).removeprefix( + '/_allauth/browser/v1/' + ) # fix refs for m_name, m_spec in p_spec.items(): From 93f846a3318c7e83ab26b932fd90fa9419abcfa5 Mon Sep 17 00:00:00 2001 From: Matthias Mair Date: Sun, 9 Feb 2025 13:46:56 +0100 Subject: [PATCH 140/156] clean up parameters that we do not use --- .../InvenTree/InvenTree/management/commands/schema.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/backend/InvenTree/InvenTree/management/commands/schema.py b/src/backend/InvenTree/InvenTree/management/commands/schema.py index 4f47ce57ebed..ceff5c4f4032 100644 --- a/src/backend/InvenTree/InvenTree/management/commands/schema.py +++ b/src/backend/InvenTree/InvenTree/management/commands/schema.py @@ -100,6 +100,11 @@ def handle(self, *args, **kwargs): for sc_name, sc_spec in c_spec.items(): new_component[prep_name(sc_name)] = self.proccess_refs(sc_spec) components[c_name] = new_component + + # Remove unused parameters + for p in dja_clean_params: + components['parameters'].pop(p.replace('#/components/parameters/', '')) + settings.SPECTACULAR_SETTINGS['APPEND_COMPONENTS'] = components super().handle(*args, **kwargs) From 3b35786d657ef59beb458c4d98b1939b589dbf1d Mon Sep 17 00:00:00 2001 From: Matthias Mair Date: Sun, 9 Feb 2025 16:12:30 +0100 Subject: [PATCH 141/156] re-add 2fa required middleware --- .../InvenTree/AllUserRequire2FAMiddleware.py | 74 +++++++++++++++++++ src/backend/InvenTree/InvenTree/middleware.py | 10 +++ src/backend/InvenTree/InvenTree/settings.py | 1 + src/backend/InvenTree/web/urls.py | 4 +- 4 files changed, 87 insertions(+), 2 deletions(-) create mode 100644 src/backend/InvenTree/InvenTree/AllUserRequire2FAMiddleware.py diff --git a/src/backend/InvenTree/InvenTree/AllUserRequire2FAMiddleware.py b/src/backend/InvenTree/InvenTree/AllUserRequire2FAMiddleware.py new file mode 100644 index 000000000000..3585bbfbdce9 --- /dev/null +++ b/src/backend/InvenTree/InvenTree/AllUserRequire2FAMiddleware.py @@ -0,0 +1,74 @@ +"""Middleware to require 2FA for users.""" + +from django.http import HttpRequest, HttpResponse, JsonResponse +from django.utils.deprecation import MiddlewareMixin +from django.utils.translation import gettext_lazy as _ + +from allauth.account.authentication import get_authentication_records +from allauth.mfa.utils import is_mfa_enabled +from allauth.mfa.webauthn.internal.flows import did_use_passwordless_login + + +def is_multifactor_logged_in(request: HttpRequest) -> bool: + """Check if the user is logged in with multifactor authentication.""" + authns = get_authentication_records(request) + print(authns) + return is_mfa_enabled(request.user) and ( + did_use_passwordless_login(request) + or any(record.get('method') == 'mfa' for record in authns) + ) + + +class AllUserRequire2FAMiddleware(MiddlewareMixin): + """Ensure that users have two-factor authentication enabled before they have access restricted endpoints. + + Adapted from https://github.com/pennersr/django-allauth/issues/3649 + """ + + allowed_pages = [ + 'api-user-meta', + 'api-user-me', + 'api-user-roles', + 'api-inventree-info', + 'api-token', + # web platform urls + 'password_reset_confirm', + 'platform', + 'platform-wildcard', + 'web-assets', + ] + app_names = ['headless'] + require_2fa_message = _( + 'You must enable two-factor authentication before doing anything else.' + ) + + def on_require_2fa(self, request: HttpRequest) -> HttpResponse: + """Force user to mfa activation.""" + return JsonResponse({'id': 'mfa_register'}, status=401) + + def is_allowed_page(self, request: HttpRequest) -> bool: + """Check if the current page can be accessed without mfa.""" + return ( + any(ref in self.app_names for ref in request.resolver_match.app_names) + or request.resolver_match.url_name in self.allowed_pages + or request.resolver_match.route == 'favicon.ico' + ) + + def enforce_2fa(self, request): + """Check if 2fa should be enforced for this request.""" + return True + + def process_view( + self, request: HttpRequest, view_func, view_args, view_kwargs + ) -> HttpResponse | None: + """If set up enforce 2fa registration.""" + if request.user.is_anonymous: + return None + if self.is_allowed_page(request): + return None + if is_multifactor_logged_in(request): + return None + + if self.enforce_2fa(request): + return self.on_require_2fa(request) + return None diff --git a/src/backend/InvenTree/InvenTree/middleware.py b/src/backend/InvenTree/InvenTree/middleware.py index d1c0688ad184..a5da9fcf969f 100644 --- a/src/backend/InvenTree/InvenTree/middleware.py +++ b/src/backend/InvenTree/InvenTree/middleware.py @@ -12,6 +12,8 @@ import structlog from error_report.middleware import ExceptionProcessor +from common.settings import get_global_setting +from InvenTree.AllUserRequire2FAMiddleware import AllUserRequire2FAMiddleware from InvenTree.cache import create_session_cache, delete_session_cache from users.models import ApiToken @@ -134,6 +136,14 @@ def __call__(self, request): return response +class Check2FAMiddleware(AllUserRequire2FAMiddleware): + """Ensure that mfa is enforced if set so.""" + + def enforce_2fa(self, request): + """Use setting to check if MFA should be enforced.""" + return get_global_setting('LOGIN_ENFORCE_MFA') + + class InvenTreeRemoteUserMiddleware(PersistentRemoteUserMiddleware): """Middleware to check if HTTP-header based auth is enabled and to set it up.""" diff --git a/src/backend/InvenTree/InvenTree/settings.py b/src/backend/InvenTree/InvenTree/settings.py index 38877c525e36..dc885769644a 100644 --- a/src/backend/InvenTree/InvenTree/settings.py +++ b/src/backend/InvenTree/InvenTree/settings.py @@ -328,6 +328,7 @@ 'django.contrib.messages.middleware.MessageMiddleware', 'django.middleware.clickjacking.XFrameOptionsMiddleware', 'InvenTree.middleware.AuthRequiredMiddleware', + 'InvenTree.middleware.Check2FAMiddleware', # Check if the user should be forced to use MFA 'maintenance_mode.middleware.MaintenanceModeMiddleware', 'InvenTree.middleware.InvenTreeExceptionProcessor', # Error reporting 'InvenTree.middleware.InvenTreeRequestCacheMiddleware', # Request caching diff --git a/src/backend/InvenTree/web/urls.py b/src/backend/InvenTree/web/urls.py index 208dd7d51205..97c8bbe59fde 100644 --- a/src/backend/InvenTree/web/urls.py +++ b/src/backend/InvenTree/web/urls.py @@ -18,7 +18,7 @@ def get(self, request, *args, **kwargs): spa_view = ensure_csrf_cookie(TemplateView.as_view(template_name='web/index.html')) -assets_path = path('assets/', RedirectAssetView.as_view()) +assets_path = path('assets/', RedirectAssetView.as_view(), name='web-assets') urlpatterns = [ @@ -31,7 +31,7 @@ def get(self, request, *args, **kwargs): spa_view, name='password_reset_confirm', ), - re_path('.*', spa_view), + re_path('.*', spa_view, name='platform-wildcard'), ]), ), assets_path, From 9cf74289b80d2f4e9ed3ae86d78ce7b161d2caed Mon Sep 17 00:00:00 2001 From: Matthias Mair Date: Sun, 9 Feb 2025 16:12:47 +0100 Subject: [PATCH 142/156] fix mail sending hook --- src/backend/InvenTree/InvenTree/auth_overrides.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/backend/InvenTree/InvenTree/auth_overrides.py b/src/backend/InvenTree/InvenTree/auth_overrides.py index 80c05c65aedb..7e3e4a414f8a 100644 --- a/src/backend/InvenTree/InvenTree/auth_overrides.py +++ b/src/backend/InvenTree/InvenTree/auth_overrides.py @@ -183,7 +183,7 @@ def send_password_reset_mail(self, user, email, context): """Send the password reset mail.""" if not get_global_setting('LOGIN_ENABLE_PWD_FORGOT'): raise PermissionDenied('Password reset is disabled') - return super().send_password_reset_mail(self, user, email, context) + return super().send_password_reset_mail(user, email, context) class CustomSocialAccountAdapter(RegistrationMixin, DefaultSocialAccountAdapter): From a190cc631118761eea1d0d9e2b0bd02886720f73 Mon Sep 17 00:00:00 2001 From: Matthias Mair Date: Sun, 9 Feb 2025 16:13:36 +0100 Subject: [PATCH 143/156] fix password set texts --- src/frontend/src/pages/Auth/ResetPassword.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/frontend/src/pages/Auth/ResetPassword.tsx b/src/frontend/src/pages/Auth/ResetPassword.tsx index 24a81aed3d06..c47bb96f4238 100644 --- a/src/frontend/src/pages/Auth/ResetPassword.tsx +++ b/src/frontend/src/pages/Auth/ResetPassword.tsx @@ -99,12 +99,12 @@ export default function ResetPassword() { From 0ea06e279d6f721ec5df94526af9dc77a4097658 Mon Sep 17 00:00:00 2001 From: Matthias Mair Date: Sun, 9 Feb 2025 23:30:34 +0100 Subject: [PATCH 144/156] Add forced mfa setup --- src/frontend/src/functions/auth.tsx | 5 +- src/frontend/src/pages/Auth/MFALogin.tsx | 2 +- src/frontend/src/pages/Auth/MFASetup.tsx | 76 +++++++++++++++++++ .../AccountSettings/QrRegistrationForm.tsx | 33 ++++++++ .../AccountSettings/SecurityContent.tsx | 17 ++--- src/frontend/src/router.tsx | 2 + src/frontend/src/states/ApiState.tsx | 1 - src/frontend/src/states/SettingsState.tsx | 24 +++++- src/frontend/src/states/states.tsx | 12 ++- 9 files changed, 151 insertions(+), 21 deletions(-) create mode 100644 src/frontend/src/pages/Auth/MFASetup.tsx create mode 100644 src/frontend/src/pages/Index/Settings/AccountSettings/QrRegistrationForm.tsx diff --git a/src/frontend/src/functions/auth.tsx b/src/frontend/src/functions/auth.tsx index 39e45b4757de..75948cbdf93e 100644 --- a/src/frontend/src/functions/auth.tsx +++ b/src/frontend/src/functions/auth.tsx @@ -114,7 +114,8 @@ export const doBasicLogin = async ( if (loginDone) { await fetchUserState(); - fetchGlobalStates(); + // see if mfa registration is required + await fetchGlobalStates(navigate); } else if (!success) { clearUserState(); } @@ -239,7 +240,7 @@ export const checkLoginState = async ( message: t`Successfully logged in` }); - fetchGlobalStates(); + fetchGlobalStates(navigate); followRedirect(navigate, redirect); }; diff --git a/src/frontend/src/pages/Auth/MFALogin.tsx b/src/frontend/src/pages/Auth/MFALogin.tsx index f4b1d2adbdd8..3470bccc1152 100644 --- a/src/frontend/src/pages/Auth/MFALogin.tsx +++ b/src/frontend/src/pages/Auth/MFALogin.tsx @@ -13,7 +13,7 @@ import { useLocation, useNavigate } from 'react-router-dom'; import { LanguageContext } from '../../contexts/LanguageContext'; import { handleMfaLogin } from '../../functions/auth'; -export default function Reset() { +export default function MFALogin() { const simpleForm = useForm({ initialValues: { code: '' } }); const navigate = useNavigate(); const location = useLocation(); diff --git a/src/frontend/src/pages/Auth/MFASetup.tsx b/src/frontend/src/pages/Auth/MFASetup.tsx new file mode 100644 index 000000000000..c4c7901a559d --- /dev/null +++ b/src/frontend/src/pages/Auth/MFASetup.tsx @@ -0,0 +1,76 @@ +import { Trans, t } from '@lingui/macro'; +import { Button, Center, Container, Stack, Title } from '@mantine/core'; +import { showNotification } from '@mantine/notifications'; +import { useEffect, useState } from 'react'; +import { useLocation, useNavigate } from 'react-router-dom'; +import { LanguageContext } from '../../contexts/LanguageContext'; +import { ApiEndpoints } from '../../enums/ApiEndpoints'; +import { authApi, doLogout, followRedirect } from '../../functions/auth'; +import { apiUrl } from '../../states/ApiState'; +import { QrRegistrationForm } from '../Index/Settings/AccountSettings/QrRegistrationForm'; + +export default function MFASetup() { + const navigate = useNavigate(); + const location = useLocation(); + + const [totpQr, setTotpQr] = useState<{ totp_url: string; secret: string }>(); + const [value, setValue] = useState(''); + + const registerTotp = async () => { + await authApi(apiUrl(ApiEndpoints.auth_totp), undefined, 'get').catch( + (err) => { + if (err.status == 404 && err.response.data.meta.secret) { + setTotpQr(err.response.data.meta); + } else { + const msg = err.response.data.errors[0].message; + showNotification({ + title: t`Failed to set up MFA`, + message: msg, + color: 'red' + }); + } + } + ); + }; + + useEffect(() => { + if (!totpQr) { + registerTotp(); + } + }, [totpQr]); + + return ( + +
+ + + + <Trans>MFA Setup Required</Trans> + + + + + + +
+
+ ); +} diff --git a/src/frontend/src/pages/Index/Settings/AccountSettings/QrRegistrationForm.tsx b/src/frontend/src/pages/Index/Settings/AccountSettings/QrRegistrationForm.tsx new file mode 100644 index 000000000000..691dc4a7fde1 --- /dev/null +++ b/src/frontend/src/pages/Index/Settings/AccountSettings/QrRegistrationForm.tsx @@ -0,0 +1,33 @@ +import { Trans, t } from '@lingui/macro'; +import { Text, TextInput } from '@mantine/core'; +import { QRCode } from '../../../../components/barcodes/QRCode'; + +export function QrRegistrationForm({ + url, + secret, + value, + setValue +}: Readonly<{ + url: string; + secret: string; + value: string; + setValue: (value: string) => void; +}>) { + return ( + <> + + + Secret +
+ {secret} +
+ setValue(event.currentTarget.value)} + /> + + ); +} diff --git a/src/frontend/src/pages/Index/Settings/AccountSettings/SecurityContent.tsx b/src/frontend/src/pages/Index/Settings/AccountSettings/SecurityContent.tsx index a16375f15ccf..712adeab70e9 100644 --- a/src/frontend/src/pages/Index/Settings/AccountSettings/SecurityContent.tsx +++ b/src/frontend/src/pages/Index/Settings/AccountSettings/SecurityContent.tsx @@ -21,12 +21,12 @@ import { IconAlertCircle, IconAt, IconX } from '@tabler/icons-react'; import { useQuery } from '@tanstack/react-query'; import { useMemo, useState } from 'react'; import { api } from '../../../../App'; -import { QRCode } from '../../../../components/barcodes/QRCode'; import { YesNoButton } from '../../../../components/buttons/YesNoButton'; import { ApiEndpoints } from '../../../../enums/ApiEndpoints'; import { ProviderLogin, authApi } from '../../../../functions/auth'; import { apiUrl, useServerApiState } from '../../../../states/ApiState'; import type { AuthConfig, Provider } from '../../../../states/states'; +import { QrRegistrationForm } from './QrRegistrationForm'; import { useReauth } from './useConfirm'; export function SecurityContent() { @@ -563,18 +563,11 @@ function MfaAddSection({ title={t`Register TOTP token`} > - - - Secret -
- {totpQr?.secret} -
- setValue(event.currentTarget.value)} + setValue={setValue} />