version 0.0.5.12: refactored authorization app (added JWT support)

This commit is contained in:
Anatoly 2019-08-13 14:14:18 +03:00
parent 0a35f06ff5
commit cc04a50709
7 changed files with 302 additions and 233 deletions

View File

@ -40,6 +40,16 @@ class UserQuerySet(models.QuerySet):
"""Filter only active users."""
return self.filter(is_active=switcher)
def by_access_token(self, token):
"""Find user by access token"""
return self.filter(oauth2_provider_accesstoken__token=token,
oauth2_provider_accesstoken__expires__gt=timezone.now())
def by_refresh_token(self, token):
"""Find user by access token"""
return self.filter(oauth2_provider_refreshtoken__token=token,
oauth2_provider_refreshtoken__expires__gt=timezone.now())
class User(ImageMixin, AbstractUser):
"""Base user model."""
@ -62,6 +72,17 @@ class User(ImageMixin, AbstractUser):
"""String method."""
return "%s:%s" % (self.email, self.get_short_name())
def get_user_info(self):
"""Get information about user"""
return {
"username": self.username,
"first_name": self.first_name if self.first_name else None,
"last_name": self.last_name if self.last_name else None,
"email": self.email if self.email else None,
"newsletter": self.newsletter,
"is_active": self.is_active
}
def change_status(self, switcher: bool = False):
"""Method to set user status to active or inactive"""
self.is_active = switcher

View File

@ -2,11 +2,19 @@
from django.contrib.auth import password_validation as password_validators
from rest_framework import serializers
from rest_framework import validators as rest_validators
from django.contrib.auth import authenticate
from django.conf import settings
from account import models as account_models
from authorization.models import Application
from utils import exceptions as utils_exceptions
# JWT
from rest_framework_simplejwt.tokens import RefreshToken, SlidingToken, UntypedToken
JWT_SETTINGS = settings.SIMPLE_JWT
# Mixins
class BaseAuthSerializerMixin(serializers.Serializer):
@ -14,6 +22,30 @@ class BaseAuthSerializerMixin(serializers.Serializer):
source = serializers.ChoiceField(choices=Application.SOURCES)
class JWTBaseMixin(serializers.Serializer):
"""
Mixin for JWT authentication.
Uses in serializers when need give in response access and refresh token
"""
# RESPONSE
refresh_token = serializers.CharField(read_only=True)
access_token = serializers.CharField(read_only=True)
def get_token(self):
"""Create JWT token"""
user = self.instance
token = RefreshToken.for_user(user)
token['user'] = user.get_user_info()
return token
def to_representation(self, instance):
"""Override to_representation method"""
token = self.get_token()
setattr(instance, 'refresh_token', str(token))
setattr(instance, 'access_token', str(token.access_token))
return super().to_representation(instance)
class LoginSerializerMixin(BaseAuthSerializerMixin):
"""Mixin for login serializers"""
password = serializers.CharField(write_only=True)
@ -26,7 +58,7 @@ class ClassicAuthSerializerMixin(BaseAuthSerializerMixin):
# Serializers
class SignupSerializer(BaseAuthSerializerMixin, serializers.ModelSerializer):
class SignupSerializer(JWTBaseMixin, serializers.ModelSerializer):
"""Signup serializer serializer mixin"""
# REQUEST
username = serializers.CharField(
@ -35,13 +67,13 @@ class SignupSerializer(BaseAuthSerializerMixin, serializers.ModelSerializer):
)
password = serializers.CharField(write_only=True)
email = serializers.EmailField(write_only=True)
newsletter = serializers.BooleanField()
newsletter = serializers.BooleanField(write_only=True)
class Meta:
model = account_models.User
fields = (
'username', 'first_name', 'last_name', 'password',
'newsletter', 'email', 'source'
'username', 'password', 'email', 'newsletter',
'access_token', 'refresh_token',
)
def validate_password(self, data):
@ -64,47 +96,93 @@ class SignupSerializer(BaseAuthSerializerMixin, serializers.ModelSerializer):
return obj
class LoginSerializer(BaseAuthSerializerMixin, serializers.ModelSerializer):
"""Serializer for login user"""
class LoginByUsernameSerializer(JWTBaseMixin, serializers.ModelSerializer):
"""Serializer for login user by username and password"""
username = serializers.CharField(write_only=True)
password = serializers.CharField(write_only=True)
class Meta:
"""Meta-class"""
model = account_models.User
fields = ('username', 'password', 'source')
fields = (
'username', 'password', 'refresh_token', 'access_token'
)
def validate(self, attrs):
"""Override validate method"""
username = attrs.pop('username')
password = attrs.pop('password')
user = authenticate(username=username,
password=password)
if not user:
raise utils_exceptions.UserNotFoundError()
self.instance = user
return attrs
class RefreshTokenSerializer(BaseAuthSerializerMixin):
"""Serializer for refresh token view"""
refresh_token = serializers.CharField(write_only=True)
class LoginByEmailSerializer(LoginSerializerMixin, serializers.ModelSerializer):
"""Serializer for signing up user by email"""
class LoginByEmailSerializer(JWTBaseMixin, serializers.ModelSerializer):
"""Serializer for login user"""
email = serializers.EmailField(write_only=True)
password = serializers.CharField(write_only=True)
class Meta:
"""Meta-class"""
model = account_models.User
fields = ('email', 'password', 'source')
fields = (
'email', 'password', 'refresh_token', 'access_token'
)
def validate(self, attrs):
"""Override validate method"""
email = attrs.pop('email')
password = attrs.pop('password')
try:
user = account_models.User.objects.get(email=attrs.get('email'))
attrs['username'] = user.get_username()
user = account_models.User.objects.get(email=email)
except account_models.User.DoesNotExist:
raise utils_exceptions.UserNotFoundError()
else:
user = authenticate(username=user.get_username(),
password=password)
if not user:
raise utils_exceptions.UserNotFoundError()
self.instance = user
return attrs
class LogoutSerializer(BaseAuthSerializerMixin):
"""Serializer for logout"""
class RefreshTokenSerializer(serializers.Serializer):
"""Serializer for refresh token view"""
refresh_token = serializers.CharField()
access_token = serializers.CharField(read_only=True)
def validate(self, attrs):
"""Override validate method"""
token = RefreshToken(attrs['refresh_token'])
data = {'access_token': str(token.access_token)}
if JWT_SETTINGS.get('ROTATE_REFRESH_TOKENS'):
if JWT_SETTINGS.get('BLACKLIST_AFTER_ROTATION'):
try:
# Attempt to blacklist the given refresh token
token.blacklist()
except AttributeError:
# If blacklist app not installed, `blacklist` method will
# not be present
pass
token.set_jti()
token.set_exp()
data['refresh_token'] = str(token)
return data
# OAuth
class OAuth2Serialzier(BaseAuthSerializerMixin):
"""Serializer OAuth2 authorization"""
token = serializers.CharField(max_length=255)
class OAuth2LogoutSerializer(BaseAuthSerializerMixin):
"""Serializer for logout"""

View File

@ -1,55 +1,51 @@
"""Common url routing for application authorization"""
from django.conf import settings
from django.conf.urls import url, include
from django.urls import path
from django.conf.urls import url
from oauth2_provider.views import AuthorizationView
from social_django import views as social_django_views
from rest_framework_social_oauth2 import views as drf_social_oauth2_views
from social_core.utils import setting_name
from social_django import views as social_django_views
from authorization.views import common as views
extra = getattr(settings, setting_name('TRAILING_SLASH'), True) and '/' or ''
app_name = 'oauth2'
app_name = 'auth'
urlpatterns_social_django = [
# authentication / association
url(r'^login/(?P<backend>[^/]+){0}$'.format(extra), social_django_views.auth,
name='begin'),
url(r'^authorize/?$', AuthorizationView.as_view(), name="authorize"),
url(r'^complete/(?P<backend>[^/]+){0}$'.format(extra), social_django_views.complete,
name='complete'),
# disconnection
url(r'^disconnect/(?P<backend>[^/]+){0}$'.format(extra), social_django_views.disconnect,
name='disconnect'),
url(r'^disconnect/(?P<backend>[^/]+)/(?P<association_id>\d+){0}$'
.format(extra), social_django_views.disconnect, name='disconnect_individual'),
]
urlpatterns_rest_framework_social_oauth2 = [
url(r'^authorize/?$', AuthorizationView.as_view(), name="authorize"),
url('', include('social_django.urls', namespace="social")),
url(r'^invalidate-sessions/?$', drf_social_oauth2_views.invalidate_sessions,
name="invalidate_sessions")
]
urlpatterns_api = [
# sign up
path('signup/facebook/', views.SocialSignUpView.as_view(), name='signup-facebook'),
path('signup/', views.SignUpView.as_view(), name='signup'),
# sign in
path('login/username/', views.LoginByUsernameView.as_view(), name='login-username'),
path('login/email/', views.LoginByEmailView.as_view(), name='login-email'),
urlpatterns_oauth2 = [
path('oauth2/signup/facebook/', views.OAuth2SignUpView.as_view(),
name='oauth2-signup-facebook'),
# for admin sign in page
path('token/', drf_social_oauth2_views .TokenView.as_view(), name="token"),
# logout
path('logout/', views.LogoutView.as_view(), name="logout"),
path('revoke-token/', views.RevokeTokenView.as_view(), name="revoke-token"),
# refresh token
path('refresh-token/', views.RefreshTokenView.as_view(), name="refresh-token"),
path('oauth2/token/', drf_social_oauth2_views .TokenView.as_view(),
name="token"),
]
urlpatterns = urlpatterns_api + \
urlpatterns_social_django + \
urlpatterns_rest_framework_social_oauth2
urlpatterns_jwt = [
path('signup/', views.SignUpView.as_view(),
name='signup'),
# sign in
path('login/username/', views.LoginByUsernameView.as_view(),
name='login-username'),
path('login/email/', views.LoginByEmailView.as_view(),
name='login-email'),
# refresh token
path('refresh-token/', views.RefreshTokenView.as_view(),
name="refresh-token"),
# logout
# path('logout/', views.LogoutView.as_view(),
# name="logout"),
]
urlpatterns = urlpatterns_jwt + \
urlpatterns_oauth2 + \
urlpatterns_social_django # for social oauth2

View File

@ -7,21 +7,29 @@ from django.utils.translation import gettext_lazy as _
from oauth2_provider.oauth2_backends import OAuthLibCore
from oauth2_provider.settings import oauth2_settings
from oauth2_provider.views.mixins import OAuthLibMixin
from rest_framework import generics
from rest_framework import permissions
from rest_framework import status
from rest_framework.generics import GenericAPIView
from rest_framework.response import Response
from rest_framework_social_oauth2.oauth2_backends import KeepRequestCore
from rest_framework_social_oauth2.oauth2_endpoints import SocialTokenServer
from rest_framework_simplejwt import tokens as jwt_tokens
from rest_framework.settings import settings as rest_settings
from django.utils import timezone
from rest_framework_simplejwt.utils import datetime_to_epoch
from account.models import User
from authorization.models import Application
from authorization.serializers import common as serializers
from utils import exceptions as utils_exceptions
from utils import permissions as utils_permissions
# JWT
# Mixins
class BaseViewMixin(GenericAPIView):
# OAuth2
class BaseOAuth2ViewMixin(generics.GenericAPIView):
"""BaseMixin for classic auth views"""
def get_client_id(self, source) -> str:
"""Get application client id"""
@ -43,7 +51,7 @@ class BaseViewMixin(GenericAPIView):
'detail': _('Not found an application with this source')})
class OAuth2ViewMixin(CsrfExemptMixin, OAuthLibMixin, BaseViewMixin):
class OAuth2ViewMixin(CsrfExemptMixin, OAuthLibMixin, BaseOAuth2ViewMixin):
"""Basic mixin for OAuth2 views"""
server_class = oauth2_settings.OAUTH2_SERVER_CLASS
validator_class = oauth2_settings.OAUTH2_VALIDATOR_CLASS
@ -68,8 +76,31 @@ class OAuth2ViewMixin(CsrfExemptMixin, OAuthLibMixin, BaseViewMixin):
raise utils_exceptions.ServiceError()
# Sign in
class SocialSignUpView(OAuth2ViewMixin, GenericAPIView):
# JWT
# Login base view mixin
class JWTViewMixin(generics.GenericAPIView):
"""JWT view mixin"""
def post(self, request, *args, **kwargs):
serializer = self.get_serializer(data=request.data)
serializer.is_valid(raise_exception=True)
response = Response(serializer.data, status=status.HTTP_200_OK)
if 'locale' in request.COOKIES:
# Write locale in cookie
key, value = 'locale', request.COOKIES.get('locale')
response.set_cookie(key=key, value=value)
# Write to cookie access and refresh token with secure flag
response.set_cookie(key='access_token',
value=serializer.data.get('access_token'),
secure=True)
response.set_cookie(key='refresh_token',
value=serializer.data.get('refresh_token'),
secure=True)
return response
# Serializers
# Sign in via Facebook
class OAuth2SignUpView(OAuth2ViewMixin, generics.GenericAPIView):
"""
Implements an endpoint to convert a provider token to an access token
@ -83,6 +114,18 @@ class SocialSignUpView(OAuth2ViewMixin, GenericAPIView):
permission_classes = (permissions.AllowAny, )
serializer_class = serializers.OAuth2Serialzier
def get_jwt_token(self, user: User,
access_token: str,
refresh_token: str):
"""Get JWT token"""
token = jwt_tokens.RefreshToken.for_user(user)
# Adding additional information about user to payload
token['user'] = user.get_user_info()
# Adding OAuth2 tokens to payloads
token['oauth2_fb'] = {'access_token': access_token,
'refresh_token': refresh_token}
return token
def post(self, request, *args, **kwargs):
"""Override POST method"""
# Preparing request data
@ -99,191 +142,93 @@ class SocialSignUpView(OAuth2ViewMixin, GenericAPIView):
request._request.POST[key] = value
url, headers, body, oauth2_status = self.create_token_response(request._request)
response = Response(data=json.loads(body), status=oauth2_status)
for k, v in headers.items():
response[k] = v
return response
body = json.loads(body)
# Get JWT token
if oauth2_status != status.HTTP_200_OK:
raise ValueError('status isn\'t 200')
user = User.objects.by_access_token(token=body.get('access_token'))\
.first()
token = self.get_jwt_token(user=user,
access_token=body.get('access_token'),
refresh_token=body.get('refresh_token'))
return Response(data={'refresh': str(token),
'access': str(token.access_token)},
status=status.HTTP_200_OK)
class SignUpView(OAuth2ViewMixin, GenericAPIView):
# JWT
# Sign in via username and password
class SignUpView(JWTViewMixin):
"""View for classic signup"""
permission_classes = (permissions.AllowAny, )
serializer_class = serializers.SignupSerializer
def post(self, request):
"""Post-method to sign up new user"""
def post(self, request, *args, **kwargs):
serializer = self.get_serializer(data=request.data)
serializer.is_valid(raise_exception=True)
serializer.save()
request_data = self.prepare_request_data(serializer.validated_data)
request_data.update({
'grant_type': 'password',
'username': serializer.validated_data.get('username'),
'password': serializer.validated_data.get('password'),
})
# Use the rest framework `.data` to fake the post body of the django request.
request._request.POST = request._request.POST.copy()
for key, value in request_data.items():
request._request.POST[key] = value
url, headers, body, oauth2_status = self.create_token_response(request._request)
response = Response(data=json.loads(body), status=oauth2_status)
for k, v in headers.items():
response[k] = v
response = Response(serializer.data, status=status.HTTP_201_CREATED)
if 'locale' in request.COOKIES:
# Write locale in cookie
key, value = 'locale', request.COOKIES.get('locale')
response.set_cookie(key=key, value=value)
# Write to cookie access and refresh token with secure flag
response.set_cookie(key='access_token',
value=serializer.data.get('access_token'),
secure=True)
response.set_cookie(key='refresh_token',
value=serializer.data.get('refresh_token'),
secure=True)
return response
# Login
class LoginByUsernameView(OAuth2ViewMixin, GenericAPIView):
"""
Implements an endpoint to provide access tokens
The endpoint is used in the following flows:
* Authorization code
* Password
* Client credentials
"""
# Login by username + password
class LoginByUsernameView(JWTViewMixin):
"""Login by username"""
permission_classes = (permissions.AllowAny,)
serializer_class = serializers.LoginSerializer
def post(self, request, *args, **kwargs):
# Preparing request data
serializer = self.get_serializer(data=request.data)
serializer.is_valid(raise_exception=True)
request_data = self.prepare_request_data(serializer.validated_data)
request_data.update({
'grant_type': 'password',
'username': serializer.validated_data.get('username'),
'password': serializer.validated_data.get('password'),
})
# Use the rest framework `.data` to fake the post body of the django request.
request._request.POST = request._request.POST.copy()
for key, value in request_data.items():
request._request.POST[key] = value
url, headers, body, oauth2_status = self.create_token_response(request._request)
response = Response(data=json.loads(body), status=oauth2_status)
for k, v in headers.items():
response[k] = v
return response
serializer_class = serializers.LoginByUsernameSerializer
class LoginByEmailView(LoginByUsernameView):
"""
Implements an endpoint to provide access tokens
The endpoint is used in the following flows:
* Authorization code
* Password
* Client credentials
"""
# Login by email + password
class LoginByEmailView(JWTViewMixin):
"""Login by email and password"""
permission_classes = (permissions.AllowAny,)
serializer_class = serializers.LoginByEmailSerializer
# Logout
class LogoutView(GenericAPIView):
"""Logout view"""
permission_classes = (permissions.IsAuthenticated, )
serializer_class = serializers.LogoutSerializer
def post(self, request, *args, **kwargs):
"""Method to logout user by deleting access tokens by source"""
serializer = self.get_serializer(data=request.data)
serializer.is_valid(raise_exception=True)
# Setup attributes
source = serializer.validated_data.get('source')
request.user.revoke_refresh_tokens(source=source)
return Response(status=status.HTTP_204_NO_CONTENT)
class RevokeTokenView(OAuth2ViewMixin, GenericAPIView):
"""
Implements an endpoint to revoke access or refresh tokens
"""
permission_classes = (permissions.AllowAny,)
serializer_class = serializers.OAuth2Serialzier
def post(self, request, *args, **kwargs):
# Use the rest framework `.data` to fake the post body of the django request.
# Preparing request data
serializer = self.get_serializer(data=request.data)
serializer.is_valid(raise_exception=True)
request_data = self.prepare_request_data(serializer.validated_data)
# Use the rest framework `.data` to fake the post body of the django request.
request._request.POST = request._request.POST.copy()
for key, value in request_data.items():
request._request.POST[key] = value
url, headers, body, oauth2_status = self.create_revocation_response(request._request)
response = Response(data=json.loads(body) if body else '',
status=oauth2_status if body else status.HTTP_204_NO_CONTENT)
for k, v in headers.items():
response[k] = v
return response
class RefreshTokenView(OAuth2ViewMixin, GenericAPIView):
"""
Implements an endpoint to provide access tokens
The endpoint is used in the following flows:
* Authorization code
* Password
* Client credentials
"""
permission_classes = (utils_permissions.IsAuthenticatedAndHasRefreshToken, )
# Refresh access_token
class RefreshTokenView(generics.GenericAPIView):
"""Refresh access_token"""
permission_classes = (permissions.IsAuthenticated,)
serializer_class = serializers.RefreshTokenSerializer
def post(self, request, *args, **kwargs):
# Preparing request data
"""POST method"""
serializer = self.get_serializer(data=request.data)
serializer.is_valid(raise_exception=True)
request_data = self.prepare_request_data(serializer.validated_data)
request_data.update({
'grant_type': 'refresh_token',
'refresh_token': serializer.validated_data.get('refresh_token')
})
# Use the rest framework `.data` to fake the post body of the django request.
request._request.POST = request._request.POST.copy()
for key, value in request_data.items():
request._request.POST[key] = value
url, headers, body, oauth2_status = self.create_token_response(request._request)
response = Response(data=json.loads(body), status=oauth2_status)
for k, v in headers.items():
response[k] = v
response = Response(serializer.validated_data, status=status.HTTP_200_OK)
if 'locale' in request.COOKIES:
# Write locale in cookie
key, value = 'locale', request.COOKIES.get('locale')
response.set_cookie(key=key, value=value)
# Write to cookie access and refresh token with secure flag
response.set_cookie(key='access_token',
value=serializer.data.get('access_token'),
secure=True)
response.set_cookie(key='refresh_token',
value=serializer.data.get('refresh_token'),
secure=True)
return response
# Logout
# class LogoutView(generics.GenericAPIView):
# """Logout user"""
# permission_classes = (permissions.IsAuthenticated,)
#
# def post(self, request, *args, **kwargs):
# """POST method"""
# current_datetime = timezone.now()
# token = request.headers.get('Authorization').split(' ')[::-1][0]
# access_token = jwt_tokens.AccessToken(token)
# access_token.lifetime = timezone.timedelta(seconds=1)
# return Response(status=status.HTTP_200_OK)
# Utils
class TokenView(CsrfExemptMixin, OAuthLibMixin, GenericAPIView):
"""
Implements an endpoint to provide access tokens
The endpoint is used in the following flows:
* Authorization code
* Password
* Client credentials
"""
permission_classes = (permissions.AllowAny,)
def post(self, request, *args, **kwargs):
# Use the rest framework `.data` to fake the post body of the django request.
request._request.POST = request._request.POST.copy()
for key, value in request.data.items():
request._request.POST[key] = value
url, headers, body, oauth2_status = self.create_token_response(request._request)
response = Response(data=json.loads(body), status=oauth2_status)
for k, v in headers.items():
response[k] = v
return response

View File

@ -12,6 +12,7 @@ https://docs.djangoproject.com/en/2.2/ref/settings/
import os
import sys
from datetime import timedelta
# Build paths inside the project like this: os.path.join(BASE_DIR, ...)
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
@ -67,7 +68,8 @@ EXTERNAL_APPS = [
'oauth2_provider',
'social_django',
'rest_framework_social_oauth2',
'django_extensions'
'django_extensions',
'rest_framework_simplejwt.token_blacklist'
]
@ -197,10 +199,8 @@ REST_FRAMEWORK = {
'DEFAULT_AUTHENTICATION_CLASSES': (
'rest_framework.authentication.TokenAuthentication',
'rest_framework.authentication.SessionAuthentication',
# OAuth
'oauth2_provider.contrib.rest_framework.OAuth2Authentication',
'rest_framework_social_oauth2.authentication.SocialAuthentication',
# JWT
'rest_framework_simplejwt.authentication.JWTAuthentication',
),
'DEFAULT_VERSIONING_CLASS': 'rest_framework.versioning.AcceptHeaderVersioning',
'DEFAULT_VERSION': (AVAILABLE_VERSIONS['current'],),
@ -236,8 +236,8 @@ AUTHENTICATION_BACKENDS = (
# }
# Override default OAuth2 namespace
DRFSO2_URL_NAMESPACE = 'oauth2'
SOCIAL_AUTH_URL_NAMESPACE = 'oauth2'
DRFSO2_URL_NAMESPACE = 'auth'
SOCIAL_AUTH_URL_NAMESPACE = 'auth'
OAUTH2_SOCIAL_AUTH_BACKEND_NAME = 'facebook'
OAUTH2_SOCIAL_AUTH_GRANT_TYPE = 'convert_token'
OAUTH2_PROVIDER_APPLICATION_MODEL = 'authorization.Application'
@ -328,3 +328,29 @@ RESETTING_TOKEN_EXPIRATION = 24 # hours
# CORS Config
CORS_ORIGIN_ALLOW_ALL = True
CORS_ALLOW_CREDENTIALS = False
# JWT
SIMPLE_JWT = {
'ACCESS_TOKEN_LIFETIME': timedelta(minutes=5),
'REFRESH_TOKEN_LIFETIME': timedelta(days=1),
'ROTATE_REFRESH_TOKENS': True,
'BLACKLIST_AFTER_ROTATION': True,
'ALGORITHM': 'HS256',
'SIGNING_KEY': SECRET_KEY,
'VERIFYING_KEY': None,
'AUTH_HEADER_TYPES': ('Bearer',),
'USER_ID_FIELD': 'id',
'USER_ID_CLAIM': 'user_id',
'AUTH_TOKEN_CLASSES': ('rest_framework_simplejwt.tokens.AccessToken', ),
'TOKEN_TYPE_CLAIM': 'token_type',
'JTI_CLAIM': 'jti',
'SLIDING_TOKEN_REFRESH_EXP_CLAIM': 'refresh_exp',
'SLIDING_TOKEN_LIFETIME': timedelta(minutes=5),
'SLIDING_TOKEN_REFRESH_LIFETIME': timedelta(days=1),
}

View File

@ -51,8 +51,8 @@ urlpatterns_doc = [
]
urlpatterns_social = [
path('api/oauth2/', include('authorization.urls.common')),
urlpatterns_auth = [
path('api/auth/', include('authorization.urls.common')),
]
urlpatterns = [
@ -61,7 +61,7 @@ urlpatterns = [
]
urlpatterns = urlpatterns + \
urlpatterns_social + \
urlpatterns_auth + \
static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)

View File

@ -24,4 +24,7 @@ django-rest-framework-social-oauth2==1.1.0
django-extensions==2.2.1
# CORS
django-cors-headers==3.0.2
django-cors-headers==3.0.2
# JWT
djangorestframework_simplejwt==4.3.0