mirror of
https://github.com/inventree/InvenTree.git
synced 2025-06-18 04:55:44 +00:00
remove more old stuff
This commit is contained in:
@ -15,8 +15,6 @@ from allauth.socialaccount.adapter import DefaultSocialAccountAdapter
|
||||
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 @@ class CustomSocialAccountAdapter(
|
||||
# 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.'))
|
||||
|
@ -235,8 +235,6 @@ INSTALLED_APPS = [
|
||||
'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 @@ if DEBUG:
|
||||
'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'
|
||||
|
||||
|
@ -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()
|
@ -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'))
|
||||
|
@ -10,11 +10,6 @@ from django.urls import include, path, re_path
|
||||
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 @@ from .api import (
|
||||
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 @@ apipatterns = [
|
||||
path(
|
||||
'auth/',
|
||||
include([
|
||||
re_path(
|
||||
r'^registration/account-confirm-email/(?P<key>[-:\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(
|
||||
'<int:pk>/',
|
||||
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/<int:pk>/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 @@ classic_frontendpatterns = [
|
||||
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'),
|
||||
|
@ -405,58 +405,6 @@ class EditUserView(AjaxUpdateView):
|
||||
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."""
|
||||
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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 \
|
||||
|
Reference in New Issue
Block a user