refactored account and authorization endpoints
This commit is contained in:
parent
111e859f74
commit
defe6abf4c
|
|
@ -11,8 +11,9 @@ class UserAdmin(BaseUserAdmin):
|
||||||
"""User model admin settings."""
|
"""User model admin settings."""
|
||||||
|
|
||||||
list_display = ('id', 'username', 'short_name', 'date_joined', 'is_active',
|
list_display = ('id', 'username', 'short_name', 'date_joined', 'is_active',
|
||||||
'is_staff', 'is_superuser',)
|
'is_staff', 'is_superuser', 'email_confirmed')
|
||||||
list_filter = ('is_active', 'is_staff', 'is_superuser', 'groups',)
|
list_filter = ('is_active', 'is_staff', 'is_superuser', 'email_confirmed',
|
||||||
|
'groups',)
|
||||||
search_fields = ('email', 'first_name', 'last_name')
|
search_fields = ('email', 'first_name', 'last_name')
|
||||||
readonly_fields = ('last_login', 'date_joined',)
|
readonly_fields = ('last_login', 'date_joined',)
|
||||||
fieldsets = (
|
fieldsets = (
|
||||||
|
|
|
||||||
|
|
@ -1,20 +1,159 @@
|
||||||
"""Common account serializers"""
|
"""Common account serializers"""
|
||||||
|
from django.conf import settings
|
||||||
|
from django.contrib.auth import password_validation as password_validators
|
||||||
from fcm_django.models import FCMDevice
|
from fcm_django.models import FCMDevice
|
||||||
from rest_framework import serializers, exceptions
|
from rest_framework import exceptions
|
||||||
|
from rest_framework import serializers
|
||||||
|
|
||||||
from account import models
|
from account import models, tasks
|
||||||
|
from utils import exceptions as utils_exceptions
|
||||||
|
|
||||||
|
|
||||||
# User serializers
|
# User serializers
|
||||||
class UserSerializer(serializers.ModelSerializer):
|
class UserSerializer(serializers.ModelSerializer):
|
||||||
"""User serializer."""
|
"""User serializer."""
|
||||||
|
# RESPONSE
|
||||||
|
email_confirmed = serializers.BooleanField()
|
||||||
|
|
||||||
|
# REQUEST
|
||||||
|
image = serializers.ImageField(required=False)
|
||||||
|
email = serializers.EmailField(required=False)
|
||||||
|
username = serializers.CharField(required=False)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = models.User
|
model = models.User
|
||||||
fields = [
|
fields = [
|
||||||
'first_name',
|
'image',
|
||||||
'last_name',
|
'email',
|
||||||
|
'email_confirmed',
|
||||||
|
'username',
|
||||||
]
|
]
|
||||||
|
|
||||||
|
def validate_email(self, value):
|
||||||
|
"""Validate email value"""
|
||||||
|
if value == self.instance.email:
|
||||||
|
raise serializers.ValidationError()
|
||||||
|
if not self.instance.email_confirmed:
|
||||||
|
raise serializers.ValidationError()
|
||||||
|
return value
|
||||||
|
|
||||||
|
def update(self, instance, validated_data):
|
||||||
|
"""
|
||||||
|
Override update method
|
||||||
|
"""
|
||||||
|
if 'email' in validated_data:
|
||||||
|
validated_data['email_confirmed'] = False
|
||||||
|
instance = super().update(instance, validated_data)
|
||||||
|
# Send verification link on user email for change email address
|
||||||
|
if settings.USE_CELERY:
|
||||||
|
tasks.confirm_new_email_address.delay(instance.id)
|
||||||
|
else:
|
||||||
|
tasks.confirm_new_email_address(instance.id)
|
||||||
|
return instance
|
||||||
|
|
||||||
|
|
||||||
|
class ChangePasswordSerializer(serializers.ModelSerializer):
|
||||||
|
"""Serializer for model User."""
|
||||||
|
|
||||||
|
password = serializers.CharField(write_only=True)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
"""Meta class"""
|
||||||
|
model = models.User
|
||||||
|
fields = ('password', )
|
||||||
|
|
||||||
|
def validate(self, attrs):
|
||||||
|
"""Override validate method"""
|
||||||
|
password = attrs.get('password')
|
||||||
|
try:
|
||||||
|
# Compare new password with the old ones
|
||||||
|
if self.instance.check_password(raw_password=password):
|
||||||
|
raise utils_exceptions.PasswordsAreEqual()
|
||||||
|
# Validate password
|
||||||
|
password_validators.validate_password(password=password)
|
||||||
|
except serializers.ValidationError as e:
|
||||||
|
raise serializers.ValidationError(str(e))
|
||||||
|
else:
|
||||||
|
return attrs
|
||||||
|
|
||||||
|
def update(self, instance, validated_data):
|
||||||
|
"""Override update method"""
|
||||||
|
# Update user password from instance
|
||||||
|
instance.set_password(validated_data.get('password'))
|
||||||
|
instance.save()
|
||||||
|
|
||||||
|
# Expire tokens
|
||||||
|
instance.expire_access_tokens()
|
||||||
|
instance.expire_refresh_tokens()
|
||||||
|
return instance
|
||||||
|
|
||||||
|
|
||||||
|
class ChangeEmailSerializer(serializers.ModelSerializer):
|
||||||
|
"""Change user email serializer"""
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
"""Meta class"""
|
||||||
|
model = models.User
|
||||||
|
fields = (
|
||||||
|
'email',
|
||||||
|
)
|
||||||
|
|
||||||
|
def validate_email(self, value):
|
||||||
|
"""Validate email value"""
|
||||||
|
if value == self.instance.email:
|
||||||
|
raise serializers.ValidationError()
|
||||||
|
return value
|
||||||
|
|
||||||
|
def validate(self, attrs):
|
||||||
|
"""Override validate method"""
|
||||||
|
email_confirmed = self.instance.email_confirmed
|
||||||
|
if not email_confirmed:
|
||||||
|
raise serializers.ValidationError()
|
||||||
|
return attrs
|
||||||
|
|
||||||
|
def update(self, instance, validated_data):
|
||||||
|
"""
|
||||||
|
Override update method
|
||||||
|
"""
|
||||||
|
instance.email = validated_data.get('email')
|
||||||
|
instance.email_confirmed = False
|
||||||
|
instance.save()
|
||||||
|
# Send verification link on user email for change email address
|
||||||
|
if settings.USE_CELERY:
|
||||||
|
tasks.confirm_new_email_address.delay(instance.id)
|
||||||
|
else:
|
||||||
|
tasks.confirm_new_email_address(instance.id)
|
||||||
|
return instance
|
||||||
|
|
||||||
|
|
||||||
|
class ConfirmEmailSerializer(serializers.ModelSerializer):
|
||||||
|
"""Confirm user email serializer"""
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
"""Meta class"""
|
||||||
|
model = models.User
|
||||||
|
fields = (
|
||||||
|
'email',
|
||||||
|
)
|
||||||
|
|
||||||
|
def validate(self, attrs):
|
||||||
|
"""Override validate method"""
|
||||||
|
email_confirmed = self.instance.email_confirmed
|
||||||
|
if email_confirmed:
|
||||||
|
raise serializers.ValidationError()
|
||||||
|
return attrs
|
||||||
|
|
||||||
|
def update(self, instance, validated_data):
|
||||||
|
"""
|
||||||
|
Override update method
|
||||||
|
"""
|
||||||
|
# Send verification link on user email for change email address
|
||||||
|
if settings.USE_CELERY:
|
||||||
|
tasks.confirm_new_email_address.delay(instance.id)
|
||||||
|
else:
|
||||||
|
tasks.confirm_new_email_address(instance.id)
|
||||||
|
return instance
|
||||||
|
|
||||||
|
|
||||||
# Firebase Cloud Messaging serializers
|
# Firebase Cloud Messaging serializers
|
||||||
class FCMDeviceSerializer(serializers.ModelSerializer):
|
class FCMDeviceSerializer(serializers.ModelSerializer):
|
||||||
|
|
|
||||||
|
|
@ -6,10 +6,7 @@ from django.utils.translation import gettext_lazy as _
|
||||||
from rest_framework import serializers
|
from rest_framework import serializers
|
||||||
|
|
||||||
from account import models, tasks
|
from account import models, tasks
|
||||||
from authorization.models import JWTRefreshToken
|
|
||||||
from utils import exceptions as utils_exceptions
|
from utils import exceptions as utils_exceptions
|
||||||
from utils.serializers import SourceSerializerMixin
|
|
||||||
from utils.tokens import GMRefreshToken
|
|
||||||
|
|
||||||
|
|
||||||
class PasswordResetSerializer(serializers.ModelSerializer):
|
class PasswordResetSerializer(serializers.ModelSerializer):
|
||||||
|
|
@ -94,144 +91,3 @@ class PasswordResetConfirmSerializer(serializers.ModelSerializer):
|
||||||
# Overdue instance
|
# Overdue instance
|
||||||
instance.overdue()
|
instance.overdue()
|
||||||
return instance
|
return instance
|
||||||
|
|
||||||
|
|
||||||
class ChangePasswordSerializer(serializers.ModelSerializer):
|
|
||||||
"""Serializer for model User."""
|
|
||||||
|
|
||||||
password = serializers.CharField(write_only=True)
|
|
||||||
|
|
||||||
class Meta:
|
|
||||||
"""Meta class"""
|
|
||||||
model = models.User
|
|
||||||
fields = ('password', )
|
|
||||||
|
|
||||||
def validate(self, attrs):
|
|
||||||
"""Override validate method"""
|
|
||||||
password = attrs.get('password')
|
|
||||||
try:
|
|
||||||
# Compare new password with the old ones
|
|
||||||
if self.instance.check_password(raw_password=password):
|
|
||||||
raise utils_exceptions.PasswordsAreEqual()
|
|
||||||
# Validate password
|
|
||||||
password_validators.validate_password(password=password)
|
|
||||||
except serializers.ValidationError as e:
|
|
||||||
raise serializers.ValidationError(str(e))
|
|
||||||
else:
|
|
||||||
return attrs
|
|
||||||
|
|
||||||
def update(self, instance, validated_data):
|
|
||||||
"""Override update method"""
|
|
||||||
# Update user password from instance
|
|
||||||
instance.set_password(validated_data.get('password'))
|
|
||||||
instance.save()
|
|
||||||
|
|
||||||
# Expire tokens
|
|
||||||
instance.expire_access_tokens()
|
|
||||||
instance.expire_refresh_tokens()
|
|
||||||
return instance
|
|
||||||
|
|
||||||
|
|
||||||
class ChangeEmailSerializer(serializers.ModelSerializer):
|
|
||||||
"""Change user email serializer"""
|
|
||||||
|
|
||||||
class Meta:
|
|
||||||
"""Meta class"""
|
|
||||||
model = models.User
|
|
||||||
fields = (
|
|
||||||
'email',
|
|
||||||
)
|
|
||||||
|
|
||||||
def validate_email(self, value):
|
|
||||||
"""Validate email value"""
|
|
||||||
if value == self.instance.email:
|
|
||||||
raise serializers.ValidationError()
|
|
||||||
return value
|
|
||||||
|
|
||||||
def validate(self, attrs):
|
|
||||||
"""Override validate method"""
|
|
||||||
email_confirmed = self.instance.email_confirmed
|
|
||||||
if not email_confirmed:
|
|
||||||
raise serializers.ValidationError()
|
|
||||||
return attrs
|
|
||||||
|
|
||||||
def update(self, instance, validated_data):
|
|
||||||
"""
|
|
||||||
Override update method
|
|
||||||
"""
|
|
||||||
instance.email = validated_data.get('email')
|
|
||||||
instance.email_confirmed = False
|
|
||||||
instance.save()
|
|
||||||
# Send verification link on user email for change email address
|
|
||||||
if settings.USE_CELERY:
|
|
||||||
tasks.confirm_new_email_address.delay(instance.id)
|
|
||||||
else:
|
|
||||||
tasks.confirm_new_email_address(instance.id)
|
|
||||||
return instance
|
|
||||||
|
|
||||||
|
|
||||||
class ConfirmEmailSerializer(serializers.ModelSerializer):
|
|
||||||
"""Confirm user email serializer"""
|
|
||||||
|
|
||||||
class Meta:
|
|
||||||
"""Meta class"""
|
|
||||||
model = models.User
|
|
||||||
fields = (
|
|
||||||
'email',
|
|
||||||
)
|
|
||||||
|
|
||||||
def validate(self, attrs):
|
|
||||||
"""Override validate method"""
|
|
||||||
email_confirmed = self.instance.email_confirmed
|
|
||||||
if email_confirmed:
|
|
||||||
raise serializers.ValidationError()
|
|
||||||
return attrs
|
|
||||||
|
|
||||||
def update(self, instance, validated_data):
|
|
||||||
"""
|
|
||||||
Override update method
|
|
||||||
"""
|
|
||||||
# Send verification link on user email for change email address
|
|
||||||
if settings.USE_CELERY:
|
|
||||||
tasks.confirm_new_email_address.delay(instance.id)
|
|
||||||
else:
|
|
||||||
tasks.confirm_new_email_address(instance.id)
|
|
||||||
return instance
|
|
||||||
|
|
||||||
|
|
||||||
class RefreshTokenSerializer(SourceSerializerMixin):
|
|
||||||
"""Serializer for refresh token view"""
|
|
||||||
refresh_token = serializers.CharField(read_only=True)
|
|
||||||
access_token = serializers.CharField(read_only=True)
|
|
||||||
|
|
||||||
def get_request(self):
|
|
||||||
"""Return request"""
|
|
||||||
return self.context.get('request')
|
|
||||||
|
|
||||||
def validate(self, attrs):
|
|
||||||
"""Override validate method"""
|
|
||||||
|
|
||||||
cookie_refresh_token = self.get_request().COOKIES.get('refresh_token')
|
|
||||||
# Check if refresh_token in COOKIES
|
|
||||||
if not cookie_refresh_token:
|
|
||||||
raise utils_exceptions.NotValidRefreshTokenError()
|
|
||||||
|
|
||||||
refresh_token = GMRefreshToken(cookie_refresh_token)
|
|
||||||
refresh_token_qs = JWTRefreshToken.objects.valid() \
|
|
||||||
.by_jti(jti=refresh_token.payload.get('jti'))
|
|
||||||
# Check if the user has refresh token
|
|
||||||
if not refresh_token_qs.exists():
|
|
||||||
raise utils_exceptions.NotValidRefreshTokenError()
|
|
||||||
|
|
||||||
old_refresh_token = refresh_token_qs.first()
|
|
||||||
source = old_refresh_token.source
|
|
||||||
user = old_refresh_token.user
|
|
||||||
|
|
||||||
# Expire existing tokens
|
|
||||||
old_refresh_token.expire()
|
|
||||||
old_refresh_token.access_token.expire()
|
|
||||||
|
|
||||||
# Create new one for user
|
|
||||||
response = user.create_jwt_tokens(source=source)
|
|
||||||
|
|
||||||
return response
|
|
||||||
|
|
|
||||||
|
|
@ -6,5 +6,12 @@ from account.views import common as views
|
||||||
app_name = 'account'
|
app_name = 'account'
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
path('user/', views.UserView.as_view(), name='user-get-update'),
|
path('user/', views.UserRetrieveUpdateView.as_view(),
|
||||||
|
name='user-retrieve-update'),
|
||||||
|
path('change-password/', views.ChangePasswordView.as_view(), name='change-password'),
|
||||||
|
path('change-email/confirm/<uidb64>/<token>/', views.ChangeEmailConfirmView.as_view(),
|
||||||
|
name='change-email-confirm'),
|
||||||
|
path('confirm-email/', views.ConfirmEmailView.as_view(), name='confirm-email'),
|
||||||
|
path('confirm-email/<uidb64>/<token>/', views.ConfirmInactiveEmailView.as_view(),
|
||||||
|
name='inactive-email-confirm'),
|
||||||
]
|
]
|
||||||
|
|
|
||||||
|
|
@ -7,19 +7,11 @@ from account.views import web as views
|
||||||
app_name = 'account'
|
app_name = 'account'
|
||||||
|
|
||||||
urlpatterns_api = [
|
urlpatterns_api = [
|
||||||
path('change-password/', views.ChangePasswordView.as_view(), name='change-password'),
|
|
||||||
path('reset-password/', views.PasswordResetView.as_view(), name='password-reset'),
|
path('reset-password/', views.PasswordResetView.as_view(), name='password-reset'),
|
||||||
path('form/reset-password/<uidb64>/<token>/', views.FormPasswordResetConfirmView.as_view(),
|
path('form/reset-password/<uidb64>/<token>/', views.FormPasswordResetConfirmView.as_view(),
|
||||||
name='form-password-reset-confirm'),
|
name='form-password-reset-confirm'),
|
||||||
path('form/reset-password/success/', views.FormPasswordResetSuccessView.as_view(),
|
path('form/reset-password/success/', views.FormPasswordResetSuccessView.as_view(),
|
||||||
name='form-password-reset-success'),
|
name='form-password-reset-success'),
|
||||||
path('refresh-token/', views.RefreshTokenView.as_view(), name='refresh-token'),
|
|
||||||
path('change-email/', views.ChangeEmailView.as_view(), name='change-email'),
|
|
||||||
path('change-email/confirm/<uidb64>/<token>/', views.ChangeEmailConfirmView.as_view(),
|
|
||||||
name='change-email-confirm'),
|
|
||||||
path('confirm-email/', views.ConfirmEmailView.as_view(), name='confirm-email'),
|
|
||||||
path('confirm-email/<uidb64>/<token>/', views.ConfirmInactiveEmailView.as_view(),
|
|
||||||
name='inactive-email-confirm'),
|
|
||||||
]
|
]
|
||||||
|
|
||||||
urlpatterns = urlpatterns_api + \
|
urlpatterns = urlpatterns_api + \
|
||||||
|
|
|
||||||
|
|
@ -1,17 +1,23 @@
|
||||||
"""Common account views"""
|
"""Common account views"""
|
||||||
|
from django.utils.encoding import force_text
|
||||||
|
from django.utils.http import urlsafe_base64_decode
|
||||||
from fcm_django.models import FCMDevice
|
from fcm_django.models import FCMDevice
|
||||||
from rest_framework import generics, status
|
from rest_framework import generics
|
||||||
from rest_framework import permissions
|
from rest_framework import permissions
|
||||||
from utils.permissions import IsAuthenticatedAndTokenIsValid
|
from rest_framework import status
|
||||||
from rest_framework.response import Response
|
from rest_framework.response import Response
|
||||||
|
|
||||||
from account import models
|
from account import models
|
||||||
from account.serializers import common as serializers
|
from account.serializers import common as serializers
|
||||||
|
from utils import exceptions as utils_exceptions
|
||||||
|
from utils.models import GMTokenGenerator
|
||||||
|
from utils.views import (JWTUpdateAPIView,
|
||||||
|
JWTGenericViewMixin)
|
||||||
|
|
||||||
|
|
||||||
# User views
|
# User views
|
||||||
class UserView(generics.RetrieveUpdateAPIView):
|
class UserRetrieveUpdateView(generics.RetrieveUpdateAPIView):
|
||||||
"""### User update view."""
|
"""User update view."""
|
||||||
serializer_class = serializers.UserSerializer
|
serializer_class = serializers.UserSerializer
|
||||||
queryset = models.User.objects.active()
|
queryset = models.User.objects.active()
|
||||||
|
|
||||||
|
|
@ -19,6 +25,86 @@ class UserView(generics.RetrieveUpdateAPIView):
|
||||||
return self.request.user
|
return self.request.user
|
||||||
|
|
||||||
|
|
||||||
|
class ChangePasswordView(JWTUpdateAPIView):
|
||||||
|
"""Change password view"""
|
||||||
|
serializer_class = serializers.ChangePasswordSerializer
|
||||||
|
queryset = models.User.objects.active()
|
||||||
|
|
||||||
|
def patch(self, request, *args, **kwargs):
|
||||||
|
"""Implement PUT method"""
|
||||||
|
serializer = self.get_serializer(instance=self.request.user,
|
||||||
|
data=request.data)
|
||||||
|
serializer.is_valid(raise_exception=True)
|
||||||
|
serializer.save()
|
||||||
|
return Response(status=status.HTTP_200_OK)
|
||||||
|
|
||||||
|
|
||||||
|
class ConfirmEmailView(JWTGenericViewMixin):
|
||||||
|
"""Confirm email view."""
|
||||||
|
serializer_class = serializers.ConfirmEmailSerializer
|
||||||
|
queryset = models.User.objects.all()
|
||||||
|
|
||||||
|
def patch(self, request, *args, **kwargs):
|
||||||
|
"""Implement POST-method"""
|
||||||
|
# Get user instance
|
||||||
|
instance = self.request.user
|
||||||
|
|
||||||
|
serializer = self.get_serializer(data=request.data, instance=instance)
|
||||||
|
serializer.is_valid(raise_exception=True)
|
||||||
|
serializer.save()
|
||||||
|
return Response(status=status.HTTP_200_OK)
|
||||||
|
|
||||||
|
|
||||||
|
class ChangeEmailConfirmView(JWTGenericViewMixin):
|
||||||
|
"""View for confirm changing email"""
|
||||||
|
|
||||||
|
permission_classes = (permissions.AllowAny,)
|
||||||
|
|
||||||
|
def get(self, request, *args, **kwargs):
|
||||||
|
"""Implement GET-method"""
|
||||||
|
uidb64 = kwargs.get('uidb64')
|
||||||
|
token = kwargs.get('token')
|
||||||
|
uid = force_text(urlsafe_base64_decode(uidb64))
|
||||||
|
user_qs = models.User.objects.filter(pk=uid)
|
||||||
|
if user_qs.exists():
|
||||||
|
user = user_qs.first()
|
||||||
|
if not GMTokenGenerator(GMTokenGenerator.CHANGE_EMAIL).check_token(
|
||||||
|
user, token):
|
||||||
|
raise utils_exceptions.NotValidTokenError()
|
||||||
|
# Approve email status
|
||||||
|
user.confirm_email()
|
||||||
|
# Expire user tokens
|
||||||
|
user.expire_access_tokens()
|
||||||
|
user.expire_refresh_tokens()
|
||||||
|
|
||||||
|
return Response(status=status.HTTP_200_OK)
|
||||||
|
else:
|
||||||
|
raise utils_exceptions.UserNotFoundError()
|
||||||
|
|
||||||
|
|
||||||
|
class ConfirmInactiveEmailView(generics.GenericAPIView):
|
||||||
|
"""View for confirm inactive email"""
|
||||||
|
|
||||||
|
permission_classes = (permissions.AllowAny,)
|
||||||
|
|
||||||
|
def get(self, request, *args, **kwargs):
|
||||||
|
"""Implement GET-method"""
|
||||||
|
uidb64 = kwargs.get('uidb64')
|
||||||
|
token = kwargs.get('token')
|
||||||
|
uid = force_text(urlsafe_base64_decode(uidb64))
|
||||||
|
user_qs = models.User.objects.filter(pk=uid)
|
||||||
|
if user_qs.exists():
|
||||||
|
user = user_qs.first()
|
||||||
|
if not GMTokenGenerator(GMTokenGenerator.CHANGE_EMAIL).check_token(
|
||||||
|
user, token):
|
||||||
|
raise utils_exceptions.NotValidTokenError()
|
||||||
|
# Approve email status
|
||||||
|
user.confirm_email()
|
||||||
|
return Response(status=status.HTTP_200_OK)
|
||||||
|
else:
|
||||||
|
raise utils_exceptions.UserNotFoundError()
|
||||||
|
|
||||||
|
|
||||||
# Firebase Cloud Messaging
|
# Firebase Cloud Messaging
|
||||||
class FCMDeviceViewSet(generics.GenericAPIView):
|
class FCMDeviceViewSet(generics.GenericAPIView):
|
||||||
"""FCMDevice registration view.
|
"""FCMDevice registration view.
|
||||||
|
|
|
||||||
|
|
@ -12,11 +12,9 @@ from django.utils.translation import gettext_lazy as _
|
||||||
from django.views.decorators.cache import never_cache
|
from django.views.decorators.cache import never_cache
|
||||||
from django.views.decorators.debug import sensitive_post_parameters
|
from django.views.decorators.debug import sensitive_post_parameters
|
||||||
from django.views.generic.edit import FormView
|
from django.views.generic.edit import FormView
|
||||||
from rest_framework import generics
|
|
||||||
from rest_framework import permissions
|
from rest_framework import permissions
|
||||||
from rest_framework import status
|
from rest_framework import status
|
||||||
from rest_framework import views
|
from rest_framework import views
|
||||||
from rest_framework.permissions import AllowAny
|
|
||||||
from rest_framework.response import Response
|
from rest_framework.response import Response
|
||||||
|
|
||||||
from account import models
|
from account import models
|
||||||
|
|
@ -25,7 +23,6 @@ from account.serializers import web as serializers
|
||||||
from utils import exceptions as utils_exceptions
|
from utils import exceptions as utils_exceptions
|
||||||
from utils.models import GMTokenGenerator
|
from utils.models import GMTokenGenerator
|
||||||
from utils.views import (JWTCreateAPIView,
|
from utils.views import (JWTCreateAPIView,
|
||||||
JWTUpdateAPIView,
|
|
||||||
JWTGenericViewMixin)
|
JWTGenericViewMixin)
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -76,108 +73,6 @@ class PasswordResetConfirmView(JWTGenericViewMixin):
|
||||||
return Response(status=status.HTTP_200_OK)
|
return Response(status=status.HTTP_200_OK)
|
||||||
|
|
||||||
|
|
||||||
class ChangePasswordView(JWTUpdateAPIView):
|
|
||||||
"""Change password view"""
|
|
||||||
serializer_class = serializers.ChangePasswordSerializer
|
|
||||||
queryset = models.User.objects.active()
|
|
||||||
|
|
||||||
def patch(self, request, *args, **kwargs):
|
|
||||||
"""Implement PUT method"""
|
|
||||||
serializer = self.get_serializer(instance=self.request.user,
|
|
||||||
data=request.data)
|
|
||||||
serializer.is_valid(raise_exception=True)
|
|
||||||
serializer.save()
|
|
||||||
return Response(status=status.HTTP_200_OK)
|
|
||||||
|
|
||||||
|
|
||||||
class ChangeEmailView(JWTGenericViewMixin):
|
|
||||||
"""Change user email view."""
|
|
||||||
serializer_class = serializers.ChangeEmailSerializer
|
|
||||||
queryset = models.User.objects.all()
|
|
||||||
|
|
||||||
def patch(self, request, *args, **kwargs):
|
|
||||||
"""Implement POST-method"""
|
|
||||||
# Get user instance
|
|
||||||
instance = self.request.user
|
|
||||||
|
|
||||||
serializer = self.get_serializer(data=request.data, instance=instance)
|
|
||||||
serializer.is_valid(raise_exception=True)
|
|
||||||
serializer.save()
|
|
||||||
return Response(status=status.HTTP_200_OK)
|
|
||||||
|
|
||||||
|
|
||||||
class ConfirmEmailView(ChangeEmailView):
|
|
||||||
"""Confirm email view."""
|
|
||||||
serializer_class = serializers.ConfirmEmailSerializer
|
|
||||||
|
|
||||||
|
|
||||||
class ChangeEmailConfirmView(JWTGenericViewMixin):
|
|
||||||
"""View for confirm changing email"""
|
|
||||||
|
|
||||||
permission_classes = (permissions.AllowAny,)
|
|
||||||
|
|
||||||
def get(self, request, *args, **kwargs):
|
|
||||||
"""Implement GET-method"""
|
|
||||||
uidb64 = kwargs.get('uidb64')
|
|
||||||
token = kwargs.get('token')
|
|
||||||
uid = force_text(urlsafe_base64_decode(uidb64))
|
|
||||||
user_qs = models.User.objects.filter(pk=uid)
|
|
||||||
if user_qs.exists():
|
|
||||||
user = user_qs.first()
|
|
||||||
if not GMTokenGenerator(GMTokenGenerator.CHANGE_EMAIL).check_token(
|
|
||||||
user, token):
|
|
||||||
raise utils_exceptions.NotValidTokenError()
|
|
||||||
# Approve email status
|
|
||||||
user.confirm_email()
|
|
||||||
# Expire user tokens
|
|
||||||
user.expire_access_tokens()
|
|
||||||
user.expire_refresh_tokens()
|
|
||||||
|
|
||||||
return Response(status=status.HTTP_200_OK)
|
|
||||||
else:
|
|
||||||
raise utils_exceptions.UserNotFoundError()
|
|
||||||
|
|
||||||
|
|
||||||
class ConfirmInactiveEmailView(generics.GenericAPIView):
|
|
||||||
"""View for confirm inactive email"""
|
|
||||||
|
|
||||||
permission_classes = (permissions.AllowAny,)
|
|
||||||
|
|
||||||
def get(self, request, *args, **kwargs):
|
|
||||||
"""Implement GET-method"""
|
|
||||||
uidb64 = kwargs.get('uidb64')
|
|
||||||
token = kwargs.get('token')
|
|
||||||
uid = force_text(urlsafe_base64_decode(uidb64))
|
|
||||||
user_qs = models.User.objects.filter(pk=uid)
|
|
||||||
if user_qs.exists():
|
|
||||||
user = user_qs.first()
|
|
||||||
if not GMTokenGenerator(GMTokenGenerator.CHANGE_EMAIL).check_token(
|
|
||||||
user, token):
|
|
||||||
raise utils_exceptions.NotValidTokenError()
|
|
||||||
# Approve email status
|
|
||||||
user.confirm_email()
|
|
||||||
return Response(status=status.HTTP_200_OK)
|
|
||||||
else:
|
|
||||||
raise utils_exceptions.UserNotFoundError()
|
|
||||||
|
|
||||||
|
|
||||||
class RefreshTokenView(JWTGenericViewMixin):
|
|
||||||
"""Refresh access_token"""
|
|
||||||
permission_classes = (AllowAny, )
|
|
||||||
serializer_class = serializers.RefreshTokenSerializer
|
|
||||||
|
|
||||||
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_201_CREATED)
|
|
||||||
access_token = serializer.data.get('access_token')
|
|
||||||
refresh_token = serializer.data.get('refresh_token')
|
|
||||||
return self._put_cookies_in_response(
|
|
||||||
cookies=self._put_data_in_cookies(access_token=access_token,
|
|
||||||
refresh_token=refresh_token),
|
|
||||||
response=response)
|
|
||||||
|
|
||||||
|
|
||||||
# Form view
|
# Form view
|
||||||
class PasswordContextMixin:
|
class PasswordContextMixin:
|
||||||
extra_context = None
|
extra_context = None
|
||||||
|
|
|
||||||
|
|
@ -8,9 +8,11 @@ from rest_framework import validators as rest_validators
|
||||||
|
|
||||||
from account import models as account_models
|
from account import models as account_models
|
||||||
from authorization import tasks
|
from authorization import tasks
|
||||||
|
from authorization.models import JWTRefreshToken
|
||||||
from utils import exceptions as utils_exceptions
|
from utils import exceptions as utils_exceptions
|
||||||
from utils import methods as utils_methods
|
from utils import methods as utils_methods
|
||||||
from utils.serializers import SourceSerializerMixin
|
from utils.serializers import SourceSerializerMixin
|
||||||
|
from utils.tokens import GMRefreshToken
|
||||||
|
|
||||||
|
|
||||||
# Serializers
|
# Serializers
|
||||||
|
|
@ -123,6 +125,44 @@ class LogoutSerializer(SourceSerializerMixin):
|
||||||
"""Serializer for Logout endpoint."""
|
"""Serializer for Logout endpoint."""
|
||||||
|
|
||||||
|
|
||||||
|
class RefreshTokenSerializer(SourceSerializerMixin):
|
||||||
|
"""Serializer for refresh token view"""
|
||||||
|
refresh_token = serializers.CharField(read_only=True)
|
||||||
|
access_token = serializers.CharField(read_only=True)
|
||||||
|
|
||||||
|
def get_request(self):
|
||||||
|
"""Return request"""
|
||||||
|
return self.context.get('request')
|
||||||
|
|
||||||
|
def validate(self, attrs):
|
||||||
|
"""Override validate method"""
|
||||||
|
|
||||||
|
cookie_refresh_token = self.get_request().COOKIES.get('refresh_token')
|
||||||
|
# Check if refresh_token in COOKIES
|
||||||
|
if not cookie_refresh_token:
|
||||||
|
raise utils_exceptions.NotValidRefreshTokenError()
|
||||||
|
|
||||||
|
refresh_token = GMRefreshToken(cookie_refresh_token)
|
||||||
|
refresh_token_qs = JWTRefreshToken.objects.valid() \
|
||||||
|
.by_jti(jti=refresh_token.payload.get('jti'))
|
||||||
|
# Check if the user has refresh token
|
||||||
|
if not refresh_token_qs.exists():
|
||||||
|
raise utils_exceptions.NotValidRefreshTokenError()
|
||||||
|
|
||||||
|
old_refresh_token = refresh_token_qs.first()
|
||||||
|
source = old_refresh_token.source
|
||||||
|
user = old_refresh_token.user
|
||||||
|
|
||||||
|
# Expire existing tokens
|
||||||
|
old_refresh_token.expire()
|
||||||
|
old_refresh_token.access_token.expire()
|
||||||
|
|
||||||
|
# Create new one for user
|
||||||
|
response = user.create_jwt_tokens(source=source)
|
||||||
|
|
||||||
|
return response
|
||||||
|
|
||||||
|
|
||||||
# OAuth
|
# OAuth
|
||||||
class OAuth2Serialzier(SourceSerializerMixin):
|
class OAuth2Serialzier(SourceSerializerMixin):
|
||||||
"""Serializer OAuth2 authorization"""
|
"""Serializer OAuth2 authorization"""
|
||||||
|
|
|
||||||
|
|
@ -32,7 +32,8 @@ urlpatterns_jwt = [
|
||||||
path('signup/confirm/<uidb64>/<token>/', views.VerifyEmailConfirmView.as_view(),
|
path('signup/confirm/<uidb64>/<token>/', views.VerifyEmailConfirmView.as_view(),
|
||||||
name='signup-confirm'),
|
name='signup-confirm'),
|
||||||
path('login/', views.LoginByUsernameOrEmailView.as_view(), name='login'),
|
path('login/', views.LoginByUsernameOrEmailView.as_view(), name='login'),
|
||||||
path('logout/', views.LogoutView.as_view(), name="logout")
|
path('logout/', views.LogoutView.as_view(), name="logout"),
|
||||||
|
path('refresh-token/', views.RefreshTokenView.as_view(), name='refresh-token'),
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -226,3 +226,23 @@ class LogoutView(JWTGenericViewMixin):
|
||||||
access_token_obj.refresh_token.expire()
|
access_token_obj.refresh_token.expire()
|
||||||
|
|
||||||
return Response(status=status.HTTP_204_NO_CONTENT)
|
return Response(status=status.HTTP_204_NO_CONTENT)
|
||||||
|
|
||||||
|
|
||||||
|
# Refresh token
|
||||||
|
class RefreshTokenView(JWTGenericViewMixin):
|
||||||
|
"""Refresh access_token"""
|
||||||
|
permission_classes = (permissions.AllowAny, )
|
||||||
|
serializer_class = serializers.RefreshTokenSerializer
|
||||||
|
|
||||||
|
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_201_CREATED)
|
||||||
|
access_token = serializer.data.get('access_token')
|
||||||
|
refresh_token = serializer.data.get('refresh_token')
|
||||||
|
return self._put_cookies_in_response(
|
||||||
|
cookies=self._put_data_in_cookies(access_token=access_token,
|
||||||
|
refresh_token=refresh_token),
|
||||||
|
response=response)
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user